diff --git a/lib/YaeAchievementLib.vcxproj b/lib/YaeAchievementLib.vcxproj index 6fbf0d6..19d11eb 100644 --- a/lib/YaeAchievementLib.vcxproj +++ b/lib/YaeAchievementLib.vcxproj @@ -99,6 +99,7 @@ + diff --git a/lib/src/NamedPipe.h b/lib/src/NamedPipe.h new file mode 100644 index 0000000..8be4097 --- /dev/null +++ b/lib/src/NamedPipe.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include + +class NamedPipe +{ + HANDLE m_hPipe = INVALID_HANDLE_VALUE; +public: + NamedPipe(HANDLE hPipe) : m_hPipe(hPipe) {} + ~NamedPipe() { if (m_hPipe != INVALID_HANDLE_VALUE) CloseHandle(m_hPipe); } + + operator HANDLE() const { return m_hPipe; } + operator bool() const { return m_hPipe != INVALID_HANDLE_VALUE && m_hPipe != nullptr; } + NamedPipe& operator= (HANDLE hPipe) { + m_hPipe = hPipe; + return *this; + } + + bool Write(const void* data, size_t size) const + { + DWORD bytesWritten; + if (!WriteFile(m_hPipe, data, static_cast(size), &bytesWritten, nullptr) || bytesWritten != size) + return false; + return true; + } + + bool Write(std::span data) const + { + return Write(data.data(), data.size()); + } + + template + bool Write(const T& data) const + { + return Write(&data, sizeof(T)); + } + +}; \ No newline at end of file diff --git a/lib/src/dllmain.cpp b/lib/src/dllmain.cpp index a916390..1f76ad0 100644 --- a/lib/src/dllmain.cpp +++ b/lib/src/dllmain.cpp @@ -13,6 +13,26 @@ CRITICAL_SECTION CriticalSection; void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index = 0); +namespace +{ + PacketType GetPacketType(const PacketMeta* packet) + { + using namespace Globals; + const auto cmdid = packet->CmdId; + + if (AchievementId && cmdid == AchievementId) + return PacketType::Achivement; + + if (AchievementIdSet.contains(cmdid) && packet->DataLength > 500) + return PacketType::Achivement; + + if (PlayerStoreId && cmdid == PlayerStoreId) + return PacketType::Inventory; + + return PacketType::None; + } +} + namespace Hook { @@ -27,41 +47,41 @@ namespace Hook { SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true); LeaveCriticalSection(&CriticalSection); - const auto packet = reinterpret_cast(val->data()); + if (ret != 0xAB89) + return ret; - auto CheckPacket = [](const PacketMeta* packet) -> bool { - const auto cmdid = _byteswap_ushort(packet->CmdId); - const auto dataLength = _byteswap_ulong(packet->DataLength); + const auto packet = val->As(); + const auto packetType = GetPacketType(packet); + if (packetType == PacketType::None) + return ret; - if (dataLength < 500) { - return false; - } +#ifdef _DEBUG + std::println("PacketType: {}", static_cast(packetType)); + std::println("CmdId: {}", packet->CmdId); + std::println("DataLength: {}", packet->m_DataLength); + //std::println("Data: {}", Util::Base64Encode(packet->AsSpan())); +#endif - if (CmdId != 0) { - return cmdid == CmdId; - } + if (!MessagePipe.Write(packetType)) + Util::Win32ErrorDialog(1002, GetLastError()); - return DynamicCmdIds.contains(cmdid); - }; + if (!MessagePipe.Write(packet->DataLength)) + Util::Win32ErrorDialog(1003, GetLastError()); - using namespace Globals; - if (ret == 0xAB89 && CheckPacket(packet)) + if (!MessagePipe.Write(packet->AsSpan())) + Util::Win32ErrorDialog(1004, GetLastError()); + + if (!AchievementsWritten) + AchievementsWritten = packetType == PacketType::Achivement; + + if (packetType == PacketType::Inventory) + PlayerStoreWrittenCount++; + + if (AchievementsWritten && PlayerStoreWrittenCount == 2) { - const auto headLength = _byteswap_ushort(packet->HeaderLength); - const auto dataLength = _byteswap_ulong(packet->DataLength); - - std::println("CmdId: {}", _byteswap_ushort(packet->CmdId)); - std::println("DataLength: {}", dataLength); - - const auto base64 = Util::Base64Encode(packet->Data + headLength, dataLength) + "\n"; - std::println("Base64: {}", base64); - #ifdef _DEBUG system("pause"); #endif - - WriteFile(MessagePipe, base64.c_str(), (DWORD)base64.length(), nullptr, nullptr); - CloseHandle(MessagePipe); ExitProcess(0); } @@ -97,11 +117,6 @@ void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index) return; } - if (!BaseAddress || Offset.BitConverter_ToUInt16 <= BaseAddress) { - // not initialized yet - return; - } - CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; GetThreadContext(thread, &ctx); @@ -120,6 +135,7 @@ DWORD __stdcall ThreadProc(LPVOID hInstance) #ifdef _DEBUG AllocConsole(); freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); + system("pause"); #endif InitializeCriticalSection(&CriticalSection); @@ -132,10 +148,11 @@ DWORD __stdcall ThreadProc(LPVOID hInstance) SwitchToThread(); } - initFuture.get(); + if (!initFuture.get()) + ExitProcess(0); MessagePipe = CreateFileA(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); - if (MessagePipe == INVALID_HANDLE_VALUE) + if (!MessagePipe) { #ifdef _DEBUG std::println("CreateFile failed: {}", GetLastError()); diff --git a/lib/src/globals.h b/lib/src/globals.h index 5501158..eecf2f5 100644 --- a/lib/src/globals.h +++ b/lib/src/globals.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include "NamedPipe.h" #define PROPERTY2(type, name, cn, os) \ type name##_cn = cn; \ @@ -12,13 +13,19 @@ namespace Globals { inline HWND GameWindow = nullptr; - inline HANDLE MessagePipe = nullptr; + inline NamedPipe MessagePipe = nullptr; inline bool IsCNREL = true; inline uintptr_t BaseAddress = 0; // 5.1.0 - 24082 - inline uint16_t CmdId = 0; // use non-zero to override dynamic search - inline std::unordered_set DynamicCmdIds; + inline uint16_t AchievementId = 0; // use non-zero to override dynamic search + inline std::unordered_set AchievementIdSet; + + // 5.3.0 - 23233 + inline uint16_t PlayerStoreId = 0; // use non-zero to override dynamic search + + inline bool AchievementsWritten = false; + inline int32_t PlayerStoreWrittenCount = 0; class Offsets { diff --git a/lib/src/il2cpp-init.cpp b/lib/src/il2cpp-init.cpp index 65596eb..a627aeb 100644 --- a/lib/src/il2cpp-init.cpp +++ b/lib/src/il2cpp-init.cpp @@ -68,8 +68,9 @@ namespace /// decodes all instruction until next push, ignores branching /// /// + /// /// std::vector DecodedInstruction - std::vector DecodeFunction(uintptr_t address) + std::vector DecodeFunction(uintptr_t address, int32_t maxInstructions = -1) { using namespace Globals; @@ -109,6 +110,10 @@ namespace instructions.emplace_back(rva, instruction, operands, instruction.operand_count_visible); address += instruction.length; + + if (maxInstructions != -1 && instructions.size() >= maxInstructions) + break; + } return instructions; @@ -163,8 +168,11 @@ namespace })); } - void ResolveCmdId() + void ResolveAchivementCmdId() { + if (Globals::AchievementId != 0) + return; + const auto il2cppSection = GetSection("il2cpp"); std::println("Section Address: 0x{:X}", reinterpret_cast(il2cppSection.data())); @@ -178,7 +186,7 @@ namespace std::vector> filteredInstructions; std::ranges::copy_if( - candidates | std::views::transform(DecodeFunction), + candidates | std::views::transform([](auto va) { return DecodeFunction(va); }), std::back_inserter(filteredInstructions), [](const std::vector& instr) { return GetDataReferenceCount(instr) == 5 && GetCallCount(instr) == 10 && @@ -207,20 +215,20 @@ namespace for (const auto& cmdId : cmdIds) { - std::println("CmdId: {}", cmdId); - Globals::DynamicCmdIds.insert(static_cast(cmdId)); + std::println("AchievementId: {}", cmdId); + Globals::AchievementIdSet.insert(static_cast(cmdId)); } } - int32_t GetCallCount(uint8_t* target) + std::vector GetCalls(uint8_t* target) { const auto il2cppSection = GetSection("il2cpp"); const auto sectionAddress = reinterpret_cast(il2cppSection.data()); const auto sectionSize = il2cppSection.size(); - int32_t count = 0; + std::vector callSites; const __m128i callOpcode = _mm_set1_epi8(0xE8); const size_t simdEnd = sectionSize / 16 * 16; @@ -245,7 +253,7 @@ namespace const uintptr_t dest = sectionAddress + instruction_index + 5 + delta; if (dest == (uintptr_t)target) { - count++; + callSites.push_back(sectionAddress + instruction_index); } // clear the bit we just processed and continue with the next match @@ -253,7 +261,7 @@ namespace } } - return count; + return callSites; } uintptr_t FindFunctionEntry(uintptr_t address) // not a correct way to find function entry @@ -279,8 +287,13 @@ namespace return address; } - uintptr_t Resolve_BitConverter_ToUInt16() + void Resolve_BitConverter_ToUInt16() { + if (Globals::Offset.BitConverter_ToUInt16 != 0) { + Globals::Offset.BitConverter_ToUInt16 += Globals::BaseAddress; + return; + } + const auto il2cppSection = GetSection("il2cpp"); std::print("Section Address: 0x{:X}", reinterpret_cast(il2cppSection.data())); @@ -316,9 +329,9 @@ namespace std::vector> futures; std::ranges::transform(filteredEntries, std::back_inserter(futures), [&](uintptr_t entry) { return std::async(std::launch::async, [&](uintptr_t e) { - const auto count = GetCallCount((uint8_t*)e); + const auto callSites = GetCalls((uint8_t*)e); std::lock_guard lock(mutex); - callCounts[e] = count; + callCounts[e] = callSites.size(); }, entry); }); @@ -335,12 +348,186 @@ namespace } } - return targetEntry; + Globals::Offset.BitConverter_ToUInt16 = targetEntry; + } + + void ResolveInventoryCmdId() + { + if (Globals::PlayerStoreId != 0) + return; + + const auto il2cppSection = GetSection("il2cpp"); + std::println("Section Address: 0x{:X}", reinterpret_cast(il2cppSection.data())); + std::println("Section End: 0x{:X}", reinterpret_cast(il2cppSection.data() + il2cppSection.size())); + + /* + cmp r8d, 2 + jz 0x3B + cmd r8d, 1 + mov rax + */ + + // look for ItemModule.GetBagManagerByStoreType + const auto candidates = Util::PatternScanAll(il2cppSection, "41 83 F8 02 74 ? 41 83 F8 01 48 8B 05"); + std::println("Candidates: {}", candidates.size()); + if (candidates.empty()) + return; + + auto pGetBagManagerByStoreType = candidates.front(); + std::println("GetBagManagerByStoreType: 0x{:X}", pGetBagManagerByStoreType); + for (auto i = 0; i < 213; ++i) + { + + const auto va = pGetBagManagerByStoreType - i; + uint8_t* code = reinterpret_cast(va); + if (va % 16 == 0 && + code[0] == 0x56 && // push rsi + code[1] == 0x57) // push rdi + { + pGetBagManagerByStoreType = va; + break; + } + + } + + std::println("GetBagManagerByStoreType: 0x{:X}", pGetBagManagerByStoreType); + if (pGetBagManagerByStoreType == candidates.front()) + { + std::println("Failed to find function entry"); + return; + } + + + uintptr_t pOnPlayerStoreNotify = 0; + { + // get all calls to GetBagManagerByStoreType + auto calls = GetCalls((uint8_t*)pGetBagManagerByStoreType); + auto decodedInstructions = calls | std::views::transform([](auto va) { return DecodeFunction(va); }); + + // from the call sites, find the one that has an arbitary branch after the call + const auto targetInstructions = std::ranges::find_if(decodedInstructions, [](const std::vector& instr) { + return std::ranges::any_of(instr, [](const DecodedInstruction& i) { + return (i.Instruction.mnemonic == ZYDIS_MNEMONIC_JMP || i.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) && + i.Operands.size() == 1 && i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER; + }); + }); + + if (targetInstructions == decodedInstructions.end()) + { + std::println("Failed to find target instruction"); + return; + } + + // ItemModule.OnPlayerStoreNotify + const auto& instructions = *targetInstructions; + pOnPlayerStoreNotify = Globals::BaseAddress + instructions.front().RVA; + for (auto i = 0; i < 126; ++i) + { + + const auto va = pOnPlayerStoreNotify - i; + uint8_t* code = reinterpret_cast(va); + + if (va % 16 == 0 && + code[0] == 0x56 && // push rsi + (*(uint32_t*)&code[1] & ~0xFF000000) == _byteswap_ulong(0x4883EC00)) // sub rsp, ?? + { + pOnPlayerStoreNotify = va; + break; + } + + } + + std::println("OnPlayerStoreNotify: 0x{:X}", pOnPlayerStoreNotify); + if (pOnPlayerStoreNotify == Globals::BaseAddress + instructions.front().RVA) + { + std::println("Failed to find function entry"); + return; + } + } + + uintptr_t pOnPacket = 0; + { + // get all calls to OnPlayerStoreNotify + const auto calls = GetCalls((uint8_t*)pOnPlayerStoreNotify); + if (calls.size() != 1) + { + std::println("Failed to find call site"); + return; + } + + // ItemModule.OnPacket + pOnPacket = calls.front(); + for (auto i = 0; i < 3044; ++i) + { + + const auto va = pOnPacket - i; + uint8_t* code = reinterpret_cast(va); + + if (va % 16 == 0 && + code[0] == 0x56 && // push rsi + (*(uint32_t*)&code[1] & ~0xFF000000) == _byteswap_ulong(0x4883EC00)) // sub rsp, ?? + { + pOnPacket = va; + break; + } + + } + + if (pOnPacket == calls.front()) + { + std::println("Failed to find function entry"); + return; + } + + std::println("OnPacket: 0x{:X}", pOnPacket); + } + + const auto decodedInstructions = DecodeFunction(pOnPacket); + std::unordered_map immBranch; // + uint32_t immValue = 0; + for (const auto& i : decodedInstructions) + { + if (i.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP && + i.Operands.size() == 2 && + i.Operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + i.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE) + { + immValue = static_cast(i.Operands[1].imm.value.u); + } + + if (i.Instruction.meta.branch_type == ZYDIS_BRANCH_TYPE_NEAR && i.Operands.size() == 1) { + immBranch[immValue] = Globals::BaseAddress + i.RVA + i.Instruction.length + i.Operands[0].imm.value.s; + } + + } + + uint32_t cmdid = 0; + for (const auto& [imm, branch] : immBranch) + { + const auto instructions = DecodeFunction(branch, 10); + const auto isMatch = std::ranges::any_of(instructions, [pOnPlayerStoreNotify](const DecodedInstruction& i) { + if (i.Instruction.mnemonic != ZYDIS_MNEMONIC_CALL) + return false; + + uintptr_t destination = 0; + ZydisCalcAbsoluteAddress(&i.Instruction, i.Operands.data(), Globals::BaseAddress + i.RVA, &destination); + return destination == pOnPlayerStoreNotify; + }); + + if (!isMatch) + continue; + + cmdid = imm; + break; + } + + Globals::PlayerStoreId = static_cast(cmdid); + std::println("PlayerStoreId: {}", Globals::PlayerStoreId); } } -void InitIL2CPP() +bool InitIL2CPP() { std::string buffer; buffer.resize(MAX_PATH); @@ -358,25 +545,29 @@ void InitIL2CPP() IsCNREL = buffer.find("YuanShen.exe") != std::string::npos; BaseAddress = (uintptr_t)GetModuleHandleA(nullptr); - std::future resolveFuncFuture = std::async(std::launch::async, [] { - if (Offset.BitConverter_ToUInt16 != 0) { - Offset.BitConverter_ToUInt16 += BaseAddress; - } - else { - Offset.BitConverter_ToUInt16 = Resolve_BitConverter_ToUInt16(); - } - }); - - std::future resolveCmdIdFuture = std::async(std::launch::async, [] { - if (CmdId == 0) { - ResolveCmdId(); - } - }); + 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); resolveFuncFuture.get(); resolveCmdIdFuture.get(); + resolveInventoryFuture.get(); std::println("BaseAddress: 0x{:X}", BaseAddress); std::println("IsCNREL: {:d}", IsCNREL); std::println("BitConverter_ToUInt16: 0x{:X}", Offset.BitConverter_ToUInt16); + + if (!AchievementId && AchievementIdSet.empty()) + { + Util::ErrorDialog("Failed to resolve achievement data"); + return false; + } + + if (!PlayerStoreId) + { + Util::ErrorDialog("Failed to resolve inventory data"); + return false; + } + + return true; } diff --git a/lib/src/il2cpp-init.h b/lib/src/il2cpp-init.h index d0b0e0d..ee54cf8 100644 --- a/lib/src/il2cpp-init.h +++ b/lib/src/il2cpp-init.h @@ -1,4 +1,4 @@ #pragma once // IL2CPP application initializer -void InitIL2CPP(); +bool InitIL2CPP(); diff --git a/lib/src/il2cpp-types.h b/lib/src/il2cpp-types.h index c667da7..6903648 100644 --- a/lib/src/il2cpp-types.h +++ b/lib/src/il2cpp-types.h @@ -2,6 +2,17 @@ #include #include +#define PROPERTY_GET_CONST(type, name, funcBody) \ + type get_##name() const funcBody \ + __declspec(property(get = get_##name)) type name; + +enum class PacketType : uint8_t +{ + None = 0, + Achivement = 1, + Inventory = 2, +}; + template class Array { @@ -21,27 +32,41 @@ public: std::span AsSpan() { return { vector, max_length }; } + + template + U As() { + return reinterpret_cast(vector); + } }; static_assert(alignof(Array) == 8, "Array alignment is incorrect"); static_assert(offsetof(Array, vector) == 32, "vector offset is incorrect"); #pragma pack(push, 1) -struct PacketMeta +class PacketMeta { - uint16_t HeadMagic; - uint16_t CmdId; - uint16_t HeaderLength; - uint32_t DataLength; - uint8_t Data[1]; +public: + uint16_t m_HeadMagic; + uint16_t m_CmdId; + uint16_t m_HeaderLength; + uint32_t m_DataLength; + uint8_t m_Data[1]; + + PacketMeta() = delete; + + PROPERTY_GET_CONST(uint16_t, HeadMagic, { return _byteswap_ushort(m_HeadMagic); }); + PROPERTY_GET_CONST(uint16_t, CmdId, { return _byteswap_ushort(m_CmdId); }); + PROPERTY_GET_CONST(uint16_t, HeaderLength, { return _byteswap_ushort(m_HeaderLength); }); + PROPERTY_GET_CONST(uint32_t, DataLength, { return _byteswap_ulong(m_DataLength); }); std::span AsSpan() { - return {Data, DataLength}; + return { m_Data, DataLength }; } + }; #pragma pack(pop) -static_assert(offsetof(PacketMeta, CmdId) == 2, "CmdId offset is incorrect"); -static_assert(offsetof(PacketMeta, HeaderLength) == 4, "HeadLength offset is incorrect"); -static_assert(offsetof(PacketMeta, DataLength) == 6, "DataLength offset is incorrect"); -static_assert(offsetof(PacketMeta, Data) == 10, "Data offset is incorrect"); \ No newline at end of file +static_assert(offsetof(PacketMeta, m_CmdId) == 2, "CmdId offset is incorrect"); +static_assert(offsetof(PacketMeta, m_HeaderLength) == 4, "HeadLength offset is incorrect"); +static_assert(offsetof(PacketMeta, m_DataLength) == 6, "DataLength offset is incorrect"); +static_assert(offsetof(PacketMeta, m_Data) == 10, "Data offset is incorrect"); \ No newline at end of file