diff --git a/YaeAchievement/YaeAchievement.csproj b/YaeAchievement/YaeAchievement.csproj index 8765470..3ad0769 100644 --- a/YaeAchievement/YaeAchievement.csproj +++ b/YaeAchievement/YaeAchievement.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows + net9.0-windows enable enable preview diff --git a/YaeAchievement/res/proto/StoreData.proto b/YaeAchievement/res/proto/StoreData.proto index 0596981..de6c148 100644 --- a/YaeAchievement/res/proto/StoreData.proto +++ b/YaeAchievement/res/proto/StoreData.proto @@ -63,6 +63,10 @@ message Furniture { uint32 count = 1; } +message VirtualItem { + int64 count = 1; +} + message Item { uint32 item_id = 1; uint64 guid = 2; @@ -70,5 +74,6 @@ message Item { Material material = 5; Equip equip = 6; Furniture furniture = 7; + VirtualItem virtual_item = 255; } } diff --git a/YaeAchievement/src/GlobalVars.cs b/YaeAchievement/src/GlobalVars.cs index 7d31e11..ee88893 100644 --- a/YaeAchievement/src/GlobalVars.cs +++ b/YaeAchievement/src/GlobalVars.cs @@ -11,7 +11,6 @@ namespace YaeAchievement; public static class GlobalVars { - public static bool UnexpectedExit { get; set; } = true; public static bool PauseOnExit { get; set; } = true; public static Version AppVersion { get; } = Assembly.GetEntryAssembly()!.GetName().Version!; diff --git a/YaeAchievement/src/Injector.cs b/YaeAchievement/src/Injector.cs index 17bd44b..0755654 100644 --- a/YaeAchievement/src/Injector.cs +++ b/YaeAchievement/src/Injector.cs @@ -25,23 +25,24 @@ public static class Injector { } // todo: refactor - public static unsafe int LoadLibraryAndInject(HANDLE hProc, ReadOnlySpan libPath) { + public static unsafe int LoadLibraryAndInject(HANDLE hProc, ReadOnlySpan libPath) { fixed (char* lpModelName = "kernel32.dll") { var hKernel = Native.GetModuleHandle(lpModelName); if (hKernel.IsNull) { return new Win32Exception().PrintMsgAndReturnErrCode("GetModuleHandle fail"); } - fixed(byte* lpProcName = "LoadLibraryA"u8) { + fixed(byte* lpProcName = "LoadLibraryW"u8) { var pLoadLibrary = Native.GetProcAddress(hKernel, (PCSTR)lpProcName); if (pLoadLibrary.IsNull) { 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) { return new Win32Exception().PrintMsgAndReturnErrCode("VirtualAllocEx fail"); } 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"); } } diff --git a/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs b/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs index 967117b..1cae695 100644 --- a/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs +++ b/YaeAchievement/src/Parsers/AchievementAllDataNotify.cs @@ -25,13 +25,23 @@ public class AchievementItem { public class AchievementAllDataNotify { public List AchievementList { get; private init; } = []; + + private static AchievementAllDataNotify? Instance { get; set; } - public static bool OnReceive(byte[] bytes) { + public static bool OnReceive(BinaryReader reader) { + var bytes = reader.ReadBytes(reader.ReadInt32()); GlobalVars.AchievementDataCache.Write(bytes); - Export.Choose(ParseFrom(bytes)); + Instance = ParseFrom(bytes); return true; } + public static void OnFinish() { + if (Instance == null) { + throw new ApplicationException("No data received"); + } + Export.Choose(Instance); + } + public static AchievementAllDataNotify ParseFrom(byte[] bytes) { using var stream = new CodedInputStream(bytes); var data = new List>(); diff --git a/YaeAchievement/src/Parsers/PlayerPropNotify.cs b/YaeAchievement/src/Parsers/PlayerPropNotify.cs new file mode 100644 index 0000000..ce8d6c8 --- /dev/null +++ b/YaeAchievement/src/Parsers/PlayerPropNotify.cs @@ -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 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); + } + +} diff --git a/YaeAchievement/src/Parsers/PlayerStoreNotify.cs b/YaeAchievement/src/Parsers/PlayerStoreNotify.cs index 8fb220d..75ce9e1 100644 --- a/YaeAchievement/src/Parsers/PlayerStoreNotify.cs +++ b/YaeAchievement/src/Parsers/PlayerStoreNotify.cs @@ -1,7 +1,11 @@ -using System.Text.Json; -using Google.Protobuf; +using Google.Protobuf; using Proto; +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable CollectionNeverQueried.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global + namespace YaeAchievement.Parsers; public class PlayerStoreNotify { @@ -12,19 +16,16 @@ public class PlayerStoreNotify { public List ItemList { get; set; } = []; - public static bool OnReceive(byte[] bytes) { - #if DEBUG - var ntf = ParseFrom(bytes); - File.WriteAllText("store_data.json", JsonSerializer.Serialize(ntf, new JsonSerializerOptions { - WriteIndented = true - })); - #endif + public static PlayerStoreNotify Instance { get; } = new (); + + public static bool OnReceive(BinaryReader reader) { + var bytes = reader.ReadBytes(reader.ReadInt32()); + Instance.ParseFrom(bytes); return true; } - private static PlayerStoreNotify ParseFrom(byte[] bytes) { + private void ParseFrom(byte[] bytes) { using var stream = new CodedInputStream(bytes); - var ntf = new PlayerStoreNotify(); try { uint tag; while ((tag = stream.ReadTag()) != 0) { @@ -33,16 +34,16 @@ public class PlayerStoreNotify { case 0: { // is VarInt var value = stream.ReadUInt32(); if (value < 10) { - ntf.StoreType = (StoreType) value; + StoreType = (StoreType) value; } else { - ntf.WeightLimit = value; + WeightLimit = value; } continue; } case 2: { // is LengthDelimited using var eStream = stream.ReadLengthDelimitedAsStream(); while (eStream.PeekTag() != 0) { - ntf.ItemList.Add(Item.Parser.ParseFrom(eStream)); + ItemList.Add(Item.Parser.ParseFrom(eStream)); } break; } @@ -54,7 +55,6 @@ public class PlayerStoreNotify { File.WriteAllBytes("store_raw_data.bin", bytes); Environment.Exit(0); } - return ntf; } } diff --git a/YaeAchievement/src/Program.cs b/YaeAchievement/src/Program.cs index cca9d6f..e6bafaf 100644 --- a/YaeAchievement/src/Program.cs +++ b/YaeAchievement/src/Program.cs @@ -1,4 +1,5 @@ using System.Text; +using System.Text.Json; using YaeAchievement; using YaeAchievement.Parsers; using YaeAchievement.res; @@ -39,7 +40,17 @@ if (historyCache.LastWriteTime.AddMinutes(60) > DateTime.UtcNow && data != null) } } -StartAndWaitResult(AppConfig.GamePath, new Dictionary> { +StartAndWaitResult(AppConfig.GamePath, new Dictionary> { { 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(); }); diff --git a/YaeAchievement/src/Utils.cs b/YaeAchievement/src/Utils.cs index fb5c6ec..8672ca1 100644 --- a/YaeAchievement/src/Utils.cs +++ b/YaeAchievement/src/Utils.cs @@ -5,7 +5,6 @@ using System.Net; using System.Net.Http.Headers; using System.Net.Sockets; using System.Runtime.InteropServices; -using System.Text; using Windows.Win32; using Windows.Win32.Foundation; using Windows.Win32.System.Console; @@ -110,10 +109,7 @@ public static class Utils { Environment.Exit(0); } } - if (useLocalLib) { - Console.WriteLine(@"[DEBUG] Use local native lib."); - File.Copy(Path.Combine(GlobalVars.AppPath, "YaeAchievementLib.dll"), GlobalVars.LibFilePath, true); - } else if (info.EnableLibDownload) { + if (info.EnableLibDownload && !useLocalLib) { var data = await GetBucketFile("schicksal/lic.dll"); await File.WriteAllBytesAsync(GlobalVars.LibFilePath, data); } @@ -207,17 +203,14 @@ public static class Utils { }; } + private static bool _isUnexpectedExit; + // ReSharper disable once UnusedMethodReturnValue.Global - public static Thread StartAndWaitResult(string exePath, Dictionary> handlers) { - AppDomain.CurrentDomain.ProcessExit += (_, _) => { - try { - File.Delete(GlobalVars.LibFilePath); - } catch (Exception) { /* ignored */ } - }; + public static Thread StartAndWaitResult(string exePath, Dictionary> handlers, Action onFinish) { if (!Injector.CreateProcess(exePath, out var hProcess, out var hThread, out var pid)) { 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)) { @@ -228,7 +221,7 @@ public static class Utils { proc = Process.GetProcessById(Convert.ToInt32(pid)); proc.EnableRaisingEvents = true; proc.Exited += (_, _) => { - if (handlers.Count != 0) + if (_isUnexpectedExit) { proc = null; Console.WriteLine(App.GameProcessExit); @@ -255,10 +248,15 @@ public static class Utils { using var reader = new BinaryReader(server); while (!proc.HasExited) { var type = reader.ReadByte(); - var length = reader.ReadInt32(); // huh - var data = reader.ReadBytes(length); - if (handlers.Remove(type, out var handler)) { - handler(data); + if (type == 0xFF) { + _isUnexpectedExit = false; + onFinish(); + break; + } + if (handlers.TryGetValue(type, out var handler)) { + if (handler(reader)) { + handlers.Remove(type); + } } } }); diff --git a/lib/YaeAchievementLib.vcxproj b/lib/YaeAchievementLib.vcxproj index 06aad96..27b6b47 100644 --- a/lib/YaeAchievementLib.vcxproj +++ b/lib/YaeAchievementLib.vcxproj @@ -62,13 +62,14 @@ false stdcpplatest true + MultiThreadedDebugDLL NotSet DebugFull - copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0 + copy $(TargetPath) C:\ProgramData\Yae\YaeAchievement.dll /y @@ -92,7 +93,7 @@ DebugFull - copy $(TargetPath) $(ProjectDir)..\bin\Debug\net8.0-windows\win-x64\YaeAchievementLib.dll /y + copy $(TargetPath) C:\ProgramData\Yae\YaeAchievement.dll /y diff --git a/lib/src/dllmain.cpp b/lib/src/dllmain.cpp index 83eac29..e63f344 100644 --- a/lib/src/dllmain.cpp +++ b/lib/src/dllmain.cpp @@ -12,7 +12,7 @@ #include "ntprivate.h" 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 { @@ -43,9 +43,9 @@ namespace Hook { const auto ToUInt16 = reinterpret_cast(Offset.BitConverter_ToUInt16); EnterCriticalSection(&CriticalSection); - SetBreakpoint((HANDLE)-2, 0, false); + SetBreakpoint((HANDLE)-2, 0, false, 0); const auto ret = ToUInt16(val, startIndex); - SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true); + SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true, 0); LeaveCriticalSection(&CriticalSection); if (ret != 0xAB89) @@ -78,8 +78,10 @@ namespace Hook { if (!PlayerStoreWritten) PlayerStoreWritten = packetType == PacketType::Inventory; - if (AchievementsWritten && PlayerStoreWritten) + if (AchievementsWritten && PlayerStoreWritten && RequiredPlayerProperties.size() == 0) { + if (!MessagePipe.Write(PacketType::End)) + Util::Win32ErrorDialog(9001, GetLastError()); #ifdef _DEBUG system("pause"); #endif @@ -88,6 +90,44 @@ namespace Hook { 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(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) @@ -98,13 +138,17 @@ LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep) if (exceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) { - if (exceptionRecord->ExceptionAddress != reinterpret_cast(Offset.BitConverter_ToUInt16)) { - return EXCEPTION_CONTINUE_SEARCH; + if (exceptionRecord->ExceptionAddress == reinterpret_cast(Offset.BitConverter_ToUInt16)) { + contextRecord->Rip = reinterpret_cast(Hook::BitConverter_ToUInt16); + contextRecord->EFlags &= ~0x100; // clear the trap flag + return EXCEPTION_CONTINUE_EXECUTION; } - - contextRecord->Rip = reinterpret_cast(Hook::BitConverter_ToUInt16); - contextRecord->EFlags &= ~0x100; // clear the trap flag - return EXCEPTION_CONTINUE_EXECUTION; + if (exceptionRecord->ExceptionAddress == reinterpret_cast(Offset.AccountDataItem_UpdateNormalProp)) { + contextRecord->Rip = reinterpret_cast(Hook::AccountDataItem_UpdateNormalProp); + contextRecord->EFlags &= ~0x100; // clear the trap flag + return EXCEPTION_CONTINUE_EXECUTION; + } + 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)) { EnterCriticalSection(&CriticalSection); - SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true); + SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true, 0); + SetBreakpoint(hThread, Offset.AccountDataItem_UpdateNormalProp, true, 1); CloseHandle(hThread); LeaveCriticalSection(&CriticalSection); } diff --git a/lib/src/globals.h b/lib/src/globals.h index b84d721..9535c35 100644 --- a/lib/src/globals.h +++ b/lib/src/globals.h @@ -27,11 +27,27 @@ namespace Globals inline bool AchievementsWritten = 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 RequiredPlayerProperties = { 10015, 10022, 10016, 10023, 10025, 10026, 10042, 10043, 10053, 10058 }; + class Offsets { public: PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0, 0); //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; diff --git a/lib/src/il2cpp-init.cpp b/lib/src/il2cpp-init.cpp index 3d058dd..af93317 100644 --- a/lib/src/il2cpp-init.cpp +++ b/lib/src/il2cpp-init.cpp @@ -514,6 +514,46 @@ namespace 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(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() @@ -537,14 +577,17 @@ bool InitIL2CPP() std::future resolveFuncFuture = std::async(std::launch::async, Resolve_BitConverter_ToUInt16); std::future resolveCmdIdFuture = std::async(std::launch::async, ResolveAchivementCmdId); std::future resolveInventoryFuture = std::async(std::launch::async, ResolveInventoryCmdId); + std::future resolveUpdatePropFuture = std::async(std::launch::async, Resolve_AccountDataItem_UpdateNormalProp); resolveFuncFuture.get(); resolveCmdIdFuture.get(); resolveInventoryFuture.get(); + resolveUpdatePropFuture.get(); std::println("BaseAddress: 0x{:X}", BaseAddress); std::println("IsCNREL: {:d}", IsCNREL); std::println("BitConverter_ToUInt16: 0x{:X}", Offset.BitConverter_ToUInt16); + std::println("AccountDataItem_UpdateNormalProp: 0x{:X}", Offset.AccountDataItem_UpdateNormalProp); if (!AchievementId && AchievementIdSet.empty()) { diff --git a/lib/src/il2cpp-types.h b/lib/src/il2cpp-types.h index 25dad07..334d9cc 100644 --- a/lib/src/il2cpp-types.h +++ b/lib/src/il2cpp-types.h @@ -11,6 +11,8 @@ enum class PacketType : uint8_t None = 0, Achivement = 1, Inventory = 2, + PropData = 100, + End = 255, }; template