mirror of
https://github.com/HolographicHat/Yae.git
synced 2025-12-12 17:38:13 +08:00
[skip ci] support virtual item
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net9.0-windows</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ message Furniture {
|
|||||||
uint32 count = 1;
|
uint32 count = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message VirtualItem {
|
||||||
|
int64 count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Item {
|
message Item {
|
||||||
uint32 item_id = 1;
|
uint32 item_id = 1;
|
||||||
uint64 guid = 2;
|
uint64 guid = 2;
|
||||||
@@ -70,5 +74,6 @@ message Item {
|
|||||||
Material material = 5;
|
Material material = 5;
|
||||||
Equip equip = 6;
|
Equip equip = 6;
|
||||||
Furniture furniture = 7;
|
Furniture furniture = 7;
|
||||||
|
VirtualItem virtual_item = 255;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ namespace YaeAchievement;
|
|||||||
|
|
||||||
public static class GlobalVars {
|
public static class GlobalVars {
|
||||||
|
|
||||||
public static bool UnexpectedExit { get; set; } = true;
|
|
||||||
public static bool PauseOnExit { get; set; } = true;
|
public static bool PauseOnExit { get; set; } = true;
|
||||||
public static Version AppVersion { get; } = Assembly.GetEntryAssembly()!.GetName().Version!;
|
public static Version AppVersion { get; } = Assembly.GetEntryAssembly()!.GetName().Version!;
|
||||||
|
|
||||||
|
|||||||
@@ -25,23 +25,24 @@ public static class Injector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// todo: refactor
|
// todo: refactor
|
||||||
public static unsafe int LoadLibraryAndInject(HANDLE hProc, ReadOnlySpan<byte> libPath) {
|
public static unsafe int LoadLibraryAndInject(HANDLE hProc, ReadOnlySpan<char> libPath) {
|
||||||
fixed (char* lpModelName = "kernel32.dll") {
|
fixed (char* lpModelName = "kernel32.dll") {
|
||||||
var hKernel = Native.GetModuleHandle(lpModelName);
|
var hKernel = Native.GetModuleHandle(lpModelName);
|
||||||
if (hKernel.IsNull) {
|
if (hKernel.IsNull) {
|
||||||
return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail");
|
return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail");
|
||||||
}
|
}
|
||||||
fixed(byte* lpProcName = "LoadLibraryA"u8) {
|
fixed(byte* lpProcName = "LoadLibraryW"u8) {
|
||||||
var pLoadLibrary = Native.GetProcAddress(hKernel, (PCSTR)lpProcName);
|
var pLoadLibrary = Native.GetProcAddress(hKernel, (PCSTR)lpProcName);
|
||||||
if (pLoadLibrary.IsNull) {
|
if (pLoadLibrary.IsNull) {
|
||||||
return new Win32Exception().PrintMsgAndReturnErrCode("GetProcAddress fail");
|
return new Win32Exception().PrintMsgAndReturnErrCode("GetProcAddress fail");
|
||||||
}
|
}
|
||||||
var pBase = Native.VirtualAllocEx(hProc, default, unchecked((uint)libPath.Length + 1), VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE | VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT, PAGE_PROTECTION_FLAGS.PAGE_READWRITE);
|
var libPathByteLen = (uint) libPath.Length * 2;
|
||||||
|
var pBase = Native.VirtualAllocEx(hProc, default, libPathByteLen + 2, VIRTUAL_ALLOCATION_TYPE.MEM_RESERVE | VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT, PAGE_PROTECTION_FLAGS.PAGE_READWRITE);
|
||||||
if ((nint)pBase == 0) {
|
if ((nint)pBase == 0) {
|
||||||
return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail");
|
return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail");
|
||||||
}
|
}
|
||||||
fixed (void* lpBuffer = libPath) {
|
fixed (void* lpBuffer = libPath) {
|
||||||
if (!Native.WriteProcessMemory(hProc, pBase, lpBuffer, unchecked((uint)libPath.Length))) {
|
if (!Native.WriteProcessMemory(hProc, pBase, lpBuffer, libPathByteLen)) {
|
||||||
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
|
return new Win32Exception().PrintMsgAndReturnErrCode("WriteProcessMemory fail");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,22 @@ public class AchievementAllDataNotify {
|
|||||||
|
|
||||||
public List<AchievementItem> AchievementList { get; private init; } = [];
|
public List<AchievementItem> AchievementList { get; private init; } = [];
|
||||||
|
|
||||||
public static bool OnReceive(byte[] bytes) {
|
private static AchievementAllDataNotify? Instance { get; set; }
|
||||||
|
|
||||||
|
public static bool OnReceive(BinaryReader reader) {
|
||||||
|
var bytes = reader.ReadBytes(reader.ReadInt32());
|
||||||
GlobalVars.AchievementDataCache.Write(bytes);
|
GlobalVars.AchievementDataCache.Write(bytes);
|
||||||
Export.Choose(ParseFrom(bytes));
|
Instance = ParseFrom(bytes);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void OnFinish() {
|
||||||
|
if (Instance == null) {
|
||||||
|
throw new ApplicationException("No data received");
|
||||||
|
}
|
||||||
|
Export.Choose(Instance);
|
||||||
|
}
|
||||||
|
|
||||||
public static AchievementAllDataNotify ParseFrom(byte[] bytes) {
|
public static AchievementAllDataNotify ParseFrom(byte[] bytes) {
|
||||||
using var stream = new CodedInputStream(bytes);
|
using var stream = new CodedInputStream(bytes);
|
||||||
var data = new List<Dictionary<uint, uint>>();
|
var data = new List<Dictionary<uint, uint>>();
|
||||||
|
|||||||
106
YaeAchievement/src/Parsers/PlayerPropNotify.cs
Normal file
106
YaeAchievement/src/Parsers/PlayerPropNotify.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
using Proto;
|
||||||
|
|
||||||
|
using static YaeAchievement.Parsers.PropType;
|
||||||
|
|
||||||
|
namespace YaeAchievement.Parsers;
|
||||||
|
|
||||||
|
public enum PropType {
|
||||||
|
None = 0,
|
||||||
|
Exp = 1001,
|
||||||
|
BreakLevel = 1002,
|
||||||
|
SatiationVal = 1003,
|
||||||
|
SatiationPenaltyTime = 1004,
|
||||||
|
GearStartVal = 2001,
|
||||||
|
GearStopVal = 2002,
|
||||||
|
Level = 4001,
|
||||||
|
LastChangeAvatarTime = 10001,
|
||||||
|
MaxSpringVolume = 10002,
|
||||||
|
CurSpringVolume = 10003,
|
||||||
|
IsSpringAutoUse = 10004,
|
||||||
|
SpringAutoUsePercent = 10005,
|
||||||
|
IsFlyable = 10006,
|
||||||
|
IsWeatherLocked = 10007,
|
||||||
|
IsGameTimeLocked = 10008,
|
||||||
|
IsTransferable = 10009,
|
||||||
|
MaxStamina = 10010,
|
||||||
|
CurPersistStamina = 10011,
|
||||||
|
CurTemporaryStamina = 10012,
|
||||||
|
PlayerLevel = 10013,
|
||||||
|
PlayerExp = 10014,
|
||||||
|
PlayerHCoin = 10015,
|
||||||
|
PlayerSCoin = 10016,
|
||||||
|
PlayerMpSettingType = 10017,
|
||||||
|
IsMpModeAvailable = 10018,
|
||||||
|
PlayerWorldLevel = 10019,
|
||||||
|
PlayerResin = 10020,
|
||||||
|
PlayerWaitSubHCoin = 10022,
|
||||||
|
PlayerWaitSubSCoin = 10023,
|
||||||
|
IsOnlyMpWithPsPlayer = 10024,
|
||||||
|
PlayerMCoin = 10025,
|
||||||
|
PlayerWaitSubMCoin = 10026,
|
||||||
|
PlayerLegendaryKey = 10027,
|
||||||
|
IsHasFirstShare = 10028,
|
||||||
|
PlayerForgePoint = 10029,
|
||||||
|
CurClimateMeter = 10035,
|
||||||
|
CurClimateType = 10036,
|
||||||
|
CurClimateAreaId = 10037,
|
||||||
|
CurClimateAreaClimateType = 10038,
|
||||||
|
PlayerWorldLevelLimit = 10039,
|
||||||
|
PlayerWorldLevelAdjustCd = 10040,
|
||||||
|
PlayerLegendaryDailyTaskNum = 10041,
|
||||||
|
PlayerHomeCoin = 10042,
|
||||||
|
PlayerWaitSubHomeCoin = 10043,
|
||||||
|
IsAutoUnlockSpecificEquip = 10044,
|
||||||
|
PlayerGCGCoin = 10045,
|
||||||
|
PlayerWaitSubGCGCoin = 10046,
|
||||||
|
PlayerOnlineTime = 10047,
|
||||||
|
IsDiveable = 10048,
|
||||||
|
MaxDiveStamina = 10049,
|
||||||
|
CurPersistDiveStamina = 10050,
|
||||||
|
IsCanPutFiveStarReliquary = 10051,
|
||||||
|
IsAutoLockFiveStarReliquary = 10052,
|
||||||
|
PlayerRoleCombatCoin = 10053,
|
||||||
|
CurPhlogiston = 10054,
|
||||||
|
ReliquaryTemporaryExp = 10055,
|
||||||
|
IsMpCrossPlatformEnabled = 10056,
|
||||||
|
IsOnlyMpWithPlatformPlayer = 10057,
|
||||||
|
PlayerMusicGameBookCoin = 10058,
|
||||||
|
IsNotShowReliquaryRecommendProp = 10059,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PlayerPropNotify {
|
||||||
|
|
||||||
|
private static readonly Dictionary<PropType, double> PropMap = [];
|
||||||
|
|
||||||
|
public static bool OnReceive(BinaryReader reader) {
|
||||||
|
var propType = (PropType) reader.ReadInt32();
|
||||||
|
var propValue = reader.ReadDouble();
|
||||||
|
PropMap.Add(propType, propValue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnFinish() {
|
||||||
|
PlayerStoreNotify.Instance.ItemList.AddRange([
|
||||||
|
CreateVirtualItem(201, GetPropValue(PlayerHCoin) - GetPropValue(PlayerWaitSubHCoin)),
|
||||||
|
CreateVirtualItem(202, GetPropValue(PlayerSCoin) - GetPropValue(PlayerWaitSubSCoin)),
|
||||||
|
CreateVirtualItem(203, GetPropValue(PlayerMCoin) - GetPropValue(PlayerWaitSubMCoin)),
|
||||||
|
CreateVirtualItem(204, GetPropValue(PlayerHomeCoin) - GetPropValue(PlayerWaitSubHomeCoin)),
|
||||||
|
CreateVirtualItem(206, GetPropValue(PlayerRoleCombatCoin)),
|
||||||
|
CreateVirtualItem(207, GetPropValue(PlayerMusicGameBookCoin)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Item CreateVirtualItem(uint id, double count) {
|
||||||
|
return new Item {
|
||||||
|
ItemId = id,
|
||||||
|
VirtualItem = new VirtualItem {
|
||||||
|
Count = (long) count
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetPropValue(PropType propType) {
|
||||||
|
return PropMap.GetValueOrDefault(propType);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
using System.Text.Json;
|
using Google.Protobuf;
|
||||||
using Google.Protobuf;
|
|
||||||
using Proto;
|
using Proto;
|
||||||
|
|
||||||
|
// ReSharper disable MemberCanBePrivate.Global
|
||||||
|
// ReSharper disable CollectionNeverQueried.Global
|
||||||
|
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||||
|
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global
|
||||||
|
|
||||||
namespace YaeAchievement.Parsers;
|
namespace YaeAchievement.Parsers;
|
||||||
|
|
||||||
public class PlayerStoreNotify {
|
public class PlayerStoreNotify {
|
||||||
@@ -12,19 +16,16 @@ public class PlayerStoreNotify {
|
|||||||
|
|
||||||
public List<Item> ItemList { get; set; } = [];
|
public List<Item> ItemList { get; set; } = [];
|
||||||
|
|
||||||
public static bool OnReceive(byte[] bytes) {
|
public static PlayerStoreNotify Instance { get; } = new ();
|
||||||
#if DEBUG
|
|
||||||
var ntf = ParseFrom(bytes);
|
public static bool OnReceive(BinaryReader reader) {
|
||||||
File.WriteAllText("store_data.json", JsonSerializer.Serialize(ntf, new JsonSerializerOptions {
|
var bytes = reader.ReadBytes(reader.ReadInt32());
|
||||||
WriteIndented = true
|
Instance.ParseFrom(bytes);
|
||||||
}));
|
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PlayerStoreNotify ParseFrom(byte[] bytes) {
|
private void ParseFrom(byte[] bytes) {
|
||||||
using var stream = new CodedInputStream(bytes);
|
using var stream = new CodedInputStream(bytes);
|
||||||
var ntf = new PlayerStoreNotify();
|
|
||||||
try {
|
try {
|
||||||
uint tag;
|
uint tag;
|
||||||
while ((tag = stream.ReadTag()) != 0) {
|
while ((tag = stream.ReadTag()) != 0) {
|
||||||
@@ -33,16 +34,16 @@ public class PlayerStoreNotify {
|
|||||||
case 0: { // is VarInt
|
case 0: { // is VarInt
|
||||||
var value = stream.ReadUInt32();
|
var value = stream.ReadUInt32();
|
||||||
if (value < 10) {
|
if (value < 10) {
|
||||||
ntf.StoreType = (StoreType) value;
|
StoreType = (StoreType) value;
|
||||||
} else {
|
} else {
|
||||||
ntf.WeightLimit = value;
|
WeightLimit = value;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
case 2: { // is LengthDelimited
|
case 2: { // is LengthDelimited
|
||||||
using var eStream = stream.ReadLengthDelimitedAsStream();
|
using var eStream = stream.ReadLengthDelimitedAsStream();
|
||||||
while (eStream.PeekTag() != 0) {
|
while (eStream.PeekTag() != 0) {
|
||||||
ntf.ItemList.Add(Item.Parser.ParseFrom(eStream));
|
ItemList.Add(Item.Parser.ParseFrom(eStream));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,6 @@ public class PlayerStoreNotify {
|
|||||||
File.WriteAllBytes("store_raw_data.bin", bytes);
|
File.WriteAllBytes("store_raw_data.bin", bytes);
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
return ntf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using YaeAchievement;
|
using YaeAchievement;
|
||||||
using YaeAchievement.Parsers;
|
using YaeAchievement.Parsers;
|
||||||
using YaeAchievement.res;
|
using YaeAchievement.res;
|
||||||
@@ -39,7 +40,17 @@ if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StartAndWaitResult(AppConfig.GamePath, new Dictionary<byte, Func<byte[], bool>> {
|
StartAndWaitResult(AppConfig.GamePath, new Dictionary<byte, Func<BinaryReader, bool>> {
|
||||||
{ 1, AchievementAllDataNotify.OnReceive },
|
{ 1, AchievementAllDataNotify.OnReceive },
|
||||||
{ 2, PlayerStoreNotify.OnReceive }
|
{ 2, PlayerStoreNotify.OnReceive },
|
||||||
|
{ 100, PlayerPropNotify.OnReceive },
|
||||||
|
}, () => {
|
||||||
|
#if DEBUG
|
||||||
|
PlayerPropNotify.OnFinish();
|
||||||
|
File.WriteAllText("store_data.json", JsonSerializer.Serialize(PlayerStoreNotify.Instance, new JsonSerializerOptions {
|
||||||
|
WriteIndented = true,
|
||||||
|
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||||
|
}));
|
||||||
|
#endif
|
||||||
|
AchievementAllDataNotify.OnFinish();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using System.Net;
|
|||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using Windows.Win32;
|
using Windows.Win32;
|
||||||
using Windows.Win32.Foundation;
|
using Windows.Win32.Foundation;
|
||||||
using Windows.Win32.System.Console;
|
using Windows.Win32.System.Console;
|
||||||
@@ -110,10 +109,7 @@ public static class Utils {
|
|||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (useLocalLib) {
|
if (info.EnableLibDownload && !useLocalLib) {
|
||||||
Console.WriteLine(@"[DEBUG] Use local native lib.");
|
|
||||||
File.Copy(Path.Combine(GlobalVars.AppPath, "YaeAchievementLib.dll"), GlobalVars.LibFilePath, true);
|
|
||||||
} else if (info.EnableLibDownload) {
|
|
||||||
var data = await GetBucketFile("schicksal/lic.dll");
|
var data = await GetBucketFile("schicksal/lic.dll");
|
||||||
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data);
|
await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data);
|
||||||
}
|
}
|
||||||
@@ -207,17 +203,14 @@ public static class Utils {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool _isUnexpectedExit;
|
||||||
|
|
||||||
// ReSharper disable once UnusedMethodReturnValue.Global
|
// ReSharper disable once UnusedMethodReturnValue.Global
|
||||||
public static Thread StartAndWaitResult(string exePath, Dictionary<byte, Func<byte[], bool>> handlers) {
|
public static Thread StartAndWaitResult(string exePath, Dictionary<byte, Func<BinaryReader, bool>> handlers, Action onFinish) {
|
||||||
AppDomain.CurrentDomain.ProcessExit += (_, _) => {
|
|
||||||
try {
|
|
||||||
File.Delete(GlobalVars.LibFilePath);
|
|
||||||
} catch (Exception) { /* ignored */ }
|
|
||||||
};
|
|
||||||
if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) {
|
if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) {
|
||||||
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("ICreateProcess fail"));
|
Environment.Exit(new Win32Exception().PrintMsgAndReturnErrCode("ICreateProcess fail"));
|
||||||
}
|
}
|
||||||
if (Injector.LoadLibraryAndInject(hProcess,Encoding.UTF8.GetBytes(GlobalVars.LibFilePath)) != 0)
|
if (Injector.LoadLibraryAndInject(hProcess,GlobalVars.LibFilePath.AsSpan()) != 0)
|
||||||
{
|
{
|
||||||
if (!Native.TerminateProcess(hProcess, 0))
|
if (!Native.TerminateProcess(hProcess, 0))
|
||||||
{
|
{
|
||||||
@@ -228,7 +221,7 @@ public static class Utils {
|
|||||||
proc = Process.GetProcessById(Convert.ToInt32(pid));
|
proc = Process.GetProcessById(Convert.ToInt32(pid));
|
||||||
proc.EnableRaisingEvents = true;
|
proc.EnableRaisingEvents = true;
|
||||||
proc.Exited += (_, _) => {
|
proc.Exited += (_, _) => {
|
||||||
if (handlers.Count != 0)
|
if (_isUnexpectedExit)
|
||||||
{
|
{
|
||||||
proc = null;
|
proc = null;
|
||||||
Console.WriteLine(App.GameProcessExit);
|
Console.WriteLine(App.GameProcessExit);
|
||||||
@@ -255,10 +248,15 @@ public static class Utils {
|
|||||||
using var reader = new BinaryReader(server);
|
using var reader = new BinaryReader(server);
|
||||||
while (!proc.HasExited) {
|
while (!proc.HasExited) {
|
||||||
var type = reader.ReadByte();
|
var type = reader.ReadByte();
|
||||||
var length = reader.ReadInt32(); // huh
|
if (type == 0xFF) {
|
||||||
var data = reader.ReadBytes(length);
|
_isUnexpectedExit = false;
|
||||||
if (handlers.Remove(type, out var handler)) {
|
onFinish();
|
||||||
handler(data);
|
break;
|
||||||
|
}
|
||||||
|
if (handlers.TryGetValue(type, out var handler)) {
|
||||||
|
if (handler(reader)) {
|
||||||
|
handlers.Remove(type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -62,13 +62,14 @@
|
|||||||
<ConformanceMode>false</ConformanceMode>
|
<ConformanceMode>false</ConformanceMode>
|
||||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||||
|
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>NotSet</SubSystem>
|
<SubSystem>NotSet</SubSystem>
|
||||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||||
</Link>
|
</Link>
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0</Command>
|
<Command>copy $(TargetPath) C:\ProgramData\Yae\YaeAchievement.dll /y</Command>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||||
@@ -92,7 +93,7 @@
|
|||||||
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
|
||||||
</Link>
|
</Link>
|
||||||
<PostBuildEvent>
|
<PostBuildEvent>
|
||||||
<Command>copy $(TargetPath) $(ProjectDir)..\bin\Debug\net8.0-windows\win-x64\YaeAchievementLib.dll /y</Command>
|
<Command>copy $(TargetPath) C:\ProgramData\Yae\YaeAchievement.dll /y</Command>
|
||||||
</PostBuildEvent>
|
</PostBuildEvent>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#include "ntprivate.h"
|
#include "ntprivate.h"
|
||||||
|
|
||||||
CRITICAL_SECTION CriticalSection;
|
CRITICAL_SECTION CriticalSection;
|
||||||
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index = 0);
|
void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index);
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -43,9 +43,9 @@ namespace Hook {
|
|||||||
const auto ToUInt16 = reinterpret_cast<decltype(&BitConverter_ToUInt16)>(Offset.BitConverter_ToUInt16);
|
const auto ToUInt16 = reinterpret_cast<decltype(&BitConverter_ToUInt16)>(Offset.BitConverter_ToUInt16);
|
||||||
|
|
||||||
EnterCriticalSection(&CriticalSection);
|
EnterCriticalSection(&CriticalSection);
|
||||||
SetBreakpoint((HANDLE)-2, 0, false);
|
SetBreakpoint((HANDLE)-2, 0, false, 0);
|
||||||
const auto ret = ToUInt16(val, startIndex);
|
const auto ret = ToUInt16(val, startIndex);
|
||||||
SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true);
|
SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true, 0);
|
||||||
LeaveCriticalSection(&CriticalSection);
|
LeaveCriticalSection(&CriticalSection);
|
||||||
|
|
||||||
if (ret != 0xAB89)
|
if (ret != 0xAB89)
|
||||||
@@ -78,8 +78,10 @@ namespace Hook {
|
|||||||
if (!PlayerStoreWritten)
|
if (!PlayerStoreWritten)
|
||||||
PlayerStoreWritten = packetType == PacketType::Inventory;
|
PlayerStoreWritten = packetType == PacketType::Inventory;
|
||||||
|
|
||||||
if (AchievementsWritten && PlayerStoreWritten)
|
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
|
||||||
{
|
{
|
||||||
|
if (!MessagePipe.Write(PacketType::End))
|
||||||
|
Util::Win32ErrorDialog(9001, GetLastError());
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
system("pause");
|
system("pause");
|
||||||
#endif
|
#endif
|
||||||
@@ -88,6 +90,44 @@ namespace Hook {
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void __fastcall AccountDataItem_UpdateNormalProp(const void* __this, const int type, const double value, const double lastValue, const int state)
|
||||||
|
{
|
||||||
|
using namespace Globals;
|
||||||
|
const auto UpdateNormalProp = reinterpret_cast<decltype(&AccountDataItem_UpdateNormalProp)>(Offset.AccountDataItem_UpdateNormalProp);
|
||||||
|
|
||||||
|
EnterCriticalSection(&CriticalSection);
|
||||||
|
SetBreakpoint((HANDLE)-2, 0, false, 1);
|
||||||
|
UpdateNormalProp(__this, type, value, lastValue, state);
|
||||||
|
SetBreakpoint((HANDLE)-2, Offset.AccountDataItem_UpdateNormalProp, true, 1);
|
||||||
|
LeaveCriticalSection(&CriticalSection);
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
std::println("PropType: {}", type);
|
||||||
|
std::println("PropState: {}", state);
|
||||||
|
std::println("PropValue: {}", value);
|
||||||
|
std::println("PropLastValue: {}", lastValue);
|
||||||
|
#endif
|
||||||
|
if (RequiredPlayerProperties.erase(type) != 0)
|
||||||
|
{
|
||||||
|
if (!MessagePipe.Write(PacketType::PropData))
|
||||||
|
Util::Win32ErrorDialog(2002, GetLastError());
|
||||||
|
if (!MessagePipe.Write(type))
|
||||||
|
Util::Win32ErrorDialog(2003, GetLastError());
|
||||||
|
if (!MessagePipe.Write(value))
|
||||||
|
Util::Win32ErrorDialog(2004, GetLastError());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0)
|
||||||
|
{
|
||||||
|
if (!MessagePipe.Write(PacketType::End))
|
||||||
|
Util::Win32ErrorDialog(9001, GetLastError());
|
||||||
|
#ifdef _DEBUG
|
||||||
|
system("pause");
|
||||||
|
#endif
|
||||||
|
ExitProcess(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep)
|
LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep)
|
||||||
@@ -98,13 +138,17 @@ LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep)
|
|||||||
|
|
||||||
if (exceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
|
if (exceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP)
|
||||||
{
|
{
|
||||||
if (exceptionRecord->ExceptionAddress != reinterpret_cast<void*>(Offset.BitConverter_ToUInt16)) {
|
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.BitConverter_ToUInt16)) {
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::BitConverter_ToUInt16);
|
||||||
|
contextRecord->EFlags &= ~0x100; // clear the trap flag
|
||||||
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
}
|
}
|
||||||
|
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.AccountDataItem_UpdateNormalProp)) {
|
||||||
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::BitConverter_ToUInt16);
|
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::AccountDataItem_UpdateNormalProp);
|
||||||
contextRecord->EFlags &= ~0x100; // clear the trap flag
|
contextRecord->EFlags &= ~0x100; // clear the trap flag
|
||||||
return EXCEPTION_CONTINUE_EXECUTION;
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
|
}
|
||||||
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
@@ -178,7 +222,8 @@ DWORD __stdcall ThreadProc(LPVOID hInstance)
|
|||||||
if (const auto hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID))
|
if (const auto hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID))
|
||||||
{
|
{
|
||||||
EnterCriticalSection(&CriticalSection);
|
EnterCriticalSection(&CriticalSection);
|
||||||
SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true);
|
SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true, 0);
|
||||||
|
SetBreakpoint(hThread, Offset.AccountDataItem_UpdateNormalProp, true, 1);
|
||||||
CloseHandle(hThread);
|
CloseHandle(hThread);
|
||||||
LeaveCriticalSection(&CriticalSection);
|
LeaveCriticalSection(&CriticalSection);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,27 @@ namespace Globals
|
|||||||
inline bool AchievementsWritten = false;
|
inline bool AchievementsWritten = false;
|
||||||
inline bool PlayerStoreWritten = false;
|
inline bool PlayerStoreWritten = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PROP_PLAYER_HCOIN = 10015,
|
||||||
|
* PROP_PLAYER_WAIT_SUB_HCOIN = 10022,
|
||||||
|
* PROP_PLAYER_SCOIN = 10016,
|
||||||
|
* PROP_PLAYER_WAIT_SUB_SCOIN = 10023,
|
||||||
|
* PROP_PLAYER_MCOIN = 10025,
|
||||||
|
* PROP_PLAYER_WAIT_SUB_MCOIN = 10026,
|
||||||
|
* PROP_PLAYER_HOME_COIN = 10042,
|
||||||
|
* PROP_PLAYER_WAIT_SUB_HOME_COIN = 10043,
|
||||||
|
* PROP_PLAYER_ROLE_COMBAT_COIN = 10053,
|
||||||
|
* PROP_PLAYER_MUSIC_GAME_BOOK_COIN = 10058,
|
||||||
|
*/
|
||||||
|
inline std::unordered_set<int> RequiredPlayerProperties = { 10015, 10022, 10016, 10023, 10025, 10026, 10042, 10043, 10053, 10058 };
|
||||||
|
|
||||||
class Offsets
|
class Offsets
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0, 0);
|
PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0, 0);
|
||||||
//PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0x0F826CF0, 0x0F825F10); // use non-zero to override dynamic search
|
//PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0x0F826CF0, 0x0F825F10); // use non-zero to override dynamic search
|
||||||
|
PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0, 0);
|
||||||
|
//PROPERTY2(uintptr_t, AccountDataItem_UpdateNormalProp, 0x0D9FE060, 0x0D94D910); // use non-zero to override dynamic search
|
||||||
};
|
};
|
||||||
|
|
||||||
inline Offsets Offset;
|
inline Offsets Offset;
|
||||||
|
|||||||
@@ -514,6 +514,46 @@ namespace
|
|||||||
std::println("PlayerStoreId: {}", Globals::PlayerStoreId);
|
std::println("PlayerStoreId: {}", Globals::PlayerStoreId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Resolve_AccountDataItem_UpdateNormalProp()
|
||||||
|
{
|
||||||
|
if (Globals::Offset.AccountDataItem_UpdateNormalProp != 0) {
|
||||||
|
Globals::Offset.AccountDataItem_UpdateNormalProp += Globals::BaseAddress;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto il2cppSection = GetSection("il2cpp");
|
||||||
|
|
||||||
|
/*
|
||||||
|
add ??, 0FFFFD8EEh
|
||||||
|
cmp ??, 30h
|
||||||
|
*/
|
||||||
|
auto candidates = Util::PatternScanAll(il2cppSection, "81 ? EE D8 FF FF ? 83 ? 30");
|
||||||
|
// should have only one result
|
||||||
|
if (candidates.size() != 1)
|
||||||
|
{
|
||||||
|
std::println("Filtered Instructions: {}", candidates.size());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto fp = candidates[0];
|
||||||
|
|
||||||
|
const auto isFunctionEntry = [](uintptr_t va) -> bool {
|
||||||
|
auto* code = reinterpret_cast<uint8_t*>(va);
|
||||||
|
/* push rsi */
|
||||||
|
/* push rdi */
|
||||||
|
return (va % 16 == 0 && code[0] == 0x56 && code[1] == 0x57);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto range = std::views::iota(0, 213);
|
||||||
|
if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(fp - i); }); it != range.end()) {
|
||||||
|
fp -= *it;
|
||||||
|
} else {
|
||||||
|
std::println("Failed to find function entry");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Globals::Offset.AccountDataItem_UpdateNormalProp = fp;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InitIL2CPP()
|
bool InitIL2CPP()
|
||||||
@@ -537,14 +577,17 @@ bool InitIL2CPP()
|
|||||||
std::future<void> resolveFuncFuture = std::async(std::launch::async, Resolve_BitConverter_ToUInt16);
|
std::future<void> resolveFuncFuture = std::async(std::launch::async, Resolve_BitConverter_ToUInt16);
|
||||||
std::future<void> resolveCmdIdFuture = std::async(std::launch::async, ResolveAchivementCmdId);
|
std::future<void> resolveCmdIdFuture = std::async(std::launch::async, ResolveAchivementCmdId);
|
||||||
std::future<void> resolveInventoryFuture = std::async(std::launch::async, ResolveInventoryCmdId);
|
std::future<void> resolveInventoryFuture = std::async(std::launch::async, ResolveInventoryCmdId);
|
||||||
|
std::future<void> resolveUpdatePropFuture = std::async(std::launch::async, Resolve_AccountDataItem_UpdateNormalProp);
|
||||||
|
|
||||||
resolveFuncFuture.get();
|
resolveFuncFuture.get();
|
||||||
resolveCmdIdFuture.get();
|
resolveCmdIdFuture.get();
|
||||||
resolveInventoryFuture.get();
|
resolveInventoryFuture.get();
|
||||||
|
resolveUpdatePropFuture.get();
|
||||||
|
|
||||||
std::println("BaseAddress: 0x{:X}", BaseAddress);
|
std::println("BaseAddress: 0x{:X}", BaseAddress);
|
||||||
std::println("IsCNREL: {:d}", IsCNREL);
|
std::println("IsCNREL: {:d}", IsCNREL);
|
||||||
std::println("BitConverter_ToUInt16: 0x{:X}", Offset.BitConverter_ToUInt16);
|
std::println("BitConverter_ToUInt16: 0x{:X}", Offset.BitConverter_ToUInt16);
|
||||||
|
std::println("AccountDataItem_UpdateNormalProp: 0x{:X}", Offset.AccountDataItem_UpdateNormalProp);
|
||||||
|
|
||||||
if (!AchievementId && AchievementIdSet.empty())
|
if (!AchievementId && AchievementIdSet.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ enum class PacketType : uint8_t
|
|||||||
None = 0,
|
None = 0,
|
||||||
Achivement = 1,
|
Achivement = 1,
|
||||||
Inventory = 2,
|
Inventory = 2,
|
||||||
|
PropData = 100,
|
||||||
|
End = 255,
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
Reference in New Issue
Block a user