[skip ci] support virtual item

This commit is contained in:
HolographicHat
2025-01-20 17:32:47 +08:00
parent 35773f49f4
commit e7d21865c7
14 changed files with 292 additions and 55 deletions

View File

@@ -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>

View File

@@ -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;
} }
} }

View File

@@ -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!;

View File

@@ -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");
} }
} }

View File

@@ -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>>();

View 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);
}
}

View File

@@ -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;
} }
} }

View File

@@ -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();
}); });

View File

@@ -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);
}
} }
} }
}); });

View File

@@ -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>

View File

@@ -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,14 +138,18 @@ 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->Rip = reinterpret_cast<DWORD64>(Hook::BitConverter_ToUInt16);
contextRecord->EFlags &= ~0x100; // clear the trap flag contextRecord->EFlags &= ~0x100; // clear the trap flag
return EXCEPTION_CONTINUE_EXECUTION; return EXCEPTION_CONTINUE_EXECUTION;
} }
if (exceptionRecord->ExceptionAddress == reinterpret_cast<void*>(Offset.AccountDataItem_UpdateNormalProp)) {
contextRecord->Rip = reinterpret_cast<DWORD64>(Hook::AccountDataItem_UpdateNormalProp);
contextRecord->EFlags &= ~0x100; // clear the trap flag
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);
} }

View File

@@ -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;

View File

@@ -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())
{ {

View File

@@ -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>