diff --git a/lib/YaeAchievementLib.vcxproj b/lib/YaeAchievementLib.vcxproj
index c81ff62..19d11eb 100644
--- a/lib/YaeAchievementLib.vcxproj
+++ b/lib/YaeAchievementLib.vcxproj
@@ -60,7 +60,7 @@
true
_DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)
false
- stdcpp20
+ stdcpplatest
true
@@ -78,7 +78,7 @@
true
_AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;ZYDIS_STATIC_BUILD;ZYCORE_STATIC_BUILD;%(PreprocessorDefinitions)
false
- stdcpp20
+ stdcpplatest
true
Speed
true
@@ -99,6 +99,7 @@
+
diff --git a/lib/src/NamedPipe.h b/lib/src/NamedPipe.h
new file mode 100644
index 0000000..c1cb0f5
--- /dev/null
+++ b/lib/src/NamedPipe.h
@@ -0,0 +1,46 @@
+#pragma once
+#include
+#include
+
+template
+concept IsSpan = requires(T t) {
+ { t.data() } -> std::convertible_to;
+ { t.size() } -> std::convertible_to;
+ { t.size_bytes() } -> std::convertible_to;
+};
+
+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;
+ }
+
+ template
+ bool Write(const T data) const
+ {
+ return Write(data.data(), data.size_bytes());
+ }
+
+ 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 3f1928c..2e54aa8 100644
--- a/lib/src/dllmain.cpp
+++ b/lib/src/dllmain.cpp
@@ -1,5 +1,6 @@
// ReSharper disable CppClangTidyCertErr33C
#include
+#include
#include
#include
#include
@@ -12,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 {
@@ -26,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->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 (!PlayerStoreWritten)
+ PlayerStoreWritten = packetType == PacketType::Inventory;
+
+ if (AchievementsWritten && PlayerStoreWritten)
{
- const auto headLength = _byteswap_ushort(packet->HeaderLength);
- const auto dataLength = _byteswap_ulong(packet->DataLength);
-
- printf("CmdId: %d\n", _byteswap_ushort(packet->CmdId));
- printf("DataLength: %d\n", dataLength);
-
- const auto base64 = Util::Base64Encode(packet->Data + headLength, dataLength) + "\n";
- printf("Base64: %s\n", base64.c_str());
-
#ifdef _DEBUG
system("pause");
#endif
-
- WriteFile(MessagePipe, base64.c_str(), (DWORD)base64.length(), nullptr, nullptr);
- CloseHandle(MessagePipe);
ExitProcess(0);
}
@@ -96,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);
@@ -119,6 +135,7 @@ DWORD __stdcall ThreadProc(LPVOID hInstance)
#ifdef _DEBUG
AllocConsole();
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);
+ system("pause");
#endif
InitializeCriticalSection(&CriticalSection);
@@ -131,13 +148,14 @@ 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
- printf("CreateFile failed: %d\n", GetLastError());
+ std::println("CreateFile failed: {}", GetLastError());
#else
Util::Win32ErrorDialog(1001, GetLastError());
ExitProcess(0);
diff --git a/lib/src/globals.h b/lib/src/globals.h
index 5501158..b84d721 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 bool PlayerStoreWritten = false;
class Offsets
{
diff --git a/lib/src/il2cpp-init.cpp b/lib/src/il2cpp-init.cpp
index a14b0c6..3d058dd 100644
--- a/lib/src/il2cpp-init.cpp
+++ b/lib/src/il2cpp-init.cpp
@@ -1,4 +1,5 @@
#include
+#include
#include
#include
#include
@@ -40,11 +41,11 @@ namespace
std::vector Operands;
};
- uintptr_t GetSection(LPCSTR name, size_t* sectionSize = nullptr)
+ std::span GetSection(LPCSTR name)
{
using namespace Globals;
if (BaseAddress == 0)
- return 0;
+ return {};
const auto dosHeader = (PIMAGE_DOS_HEADER)BaseAddress;
const auto ntHeader = (PIMAGE_NT_HEADERS)((uintptr_t)dosHeader + dosHeader->e_lfanew);
@@ -54,22 +55,22 @@ namespace
{
if (strcmp((char*)sectionHeader[i].Name, name) == 0)
{
- if (sectionSize != nullptr) {
- *sectionSize = sectionHeader[i].Misc.VirtualSize;
- }
- return BaseAddress + sectionHeader[i].VirtualAddress;
+ const auto sectionSize = sectionHeader[i].Misc.VirtualSize;
+ const auto virtualAddress = BaseAddress + sectionHeader[i].VirtualAddress;
+ return std::span(reinterpret_cast(virtualAddress), sectionSize);
}
}
- return 0;
+ return {};
}
///
/// 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;
@@ -148,7 +153,7 @@ namespace
for (const auto& instr : instructions)
{
if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CALL) {
- uint32_t destination = instr.RVA + instr.Instruction.length + instr.Operands[0].imm.value.s;
+ uint32_t destination = instr.Operands[0].imm.value.s + instr.RVA + instr.Instruction.length;
calls.insert(destination);
}
}
@@ -163,66 +168,67 @@ namespace
}));
}
- void ResolveCmdId()
+ void ResolveAchivementCmdId()
{
- size_t sectionSize;
- const auto sectionAddress = GetSection("il2cpp", §ionSize);
- const auto sectionEnd = sectionAddress + sectionSize;
+ if (Globals::AchievementId != 0)
+ return;
- printf("Section Address: 0x%llX\n", sectionAddress);
- printf("Section End: 0x%llX\n", sectionEnd);
+ const auto il2cppSection = GetSection("il2cpp");
- if (sectionAddress == 0)
+ std::println("Section Address: 0x{:X}", reinterpret_cast(il2cppSection.data()));
+ std::println("Section End: 0x{:X}", reinterpret_cast(il2cppSection.data() + il2cppSection.size()));
+
+ if (il2cppSection.empty())
return; // message box?
- const auto candidates = Util::PatternScanAll(sectionAddress, sectionEnd, "56 48 83 EC 20 48 89 D0 48 89 CE 80 3D ? ? ? ? 00");
- printf("Candidates: %llu\n", candidates.size());
-
- std::vector> candidateInstructions;
- std::ranges::transform(candidates, std::back_inserter(candidateInstructions), DecodeFunction);
+ const auto candidates = Util::PatternScanAll(il2cppSection, "56 48 83 EC 20 48 89 D0 48 89 CE 80 3D ? ? ? ? 00");
+ std::println("Candidates: {}", candidates.size());
std::vector> filteredInstructions;
- std::ranges::copy_if(candidateInstructions, std::back_inserter(filteredInstructions), [](const std::vector& instr) {
- return GetDataReferenceCount(instr) == 5 && GetCallCount(instr) == 10 && GetUniqueCallCount(instr) == 6 && GetCmpImmCount(instr) == 5;
+ std::ranges::copy_if(
+ candidates | std::views::transform([](auto va) { return DecodeFunction(va); }),
+ std::back_inserter(filteredInstructions),
+ [](const std::vector& instr) {
+ return GetDataReferenceCount(instr) == 5 && GetCallCount(instr) == 10 &&
+ GetUniqueCallCount(instr) == 6 && GetCmpImmCount(instr) == 5;
});
// should have only one result
if (filteredInstructions.size() != 1)
{
- printf("Filtered Instructions: %llu\n", filteredInstructions.size());
+ std::println("Filtered Instructions: {}", filteredInstructions.size());
return;
}
const auto& instructions = filteredInstructions[0];
- printf("RVA: 0x%08X\n", instructions.front().RVA);
+ std::println("RVA: 0x{:08X}", instructions.front().RVA);
// extract all the non-zero immediate values from the cmp instructions
- std::decay_t cmpInstructions;
- std::ranges::copy_if(instructions, std::back_inserter(cmpInstructions), [](const DecodedInstruction& instr) {
- return instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP && instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && instr.Operands[1].imm.value.u;
- });
-
std::vector cmdIds;
- std::ranges::transform(cmpInstructions, std::back_inserter(cmdIds), [](const DecodedInstruction& instr) {
- return instr.Operands[1].imm.value.u;
+ std::ranges::for_each(instructions, [&cmdIds](const DecodedInstruction& instr) {
+ if (instr.Instruction.mnemonic == ZYDIS_MNEMONIC_CMP &&
+ instr.Operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
+ instr.Operands[1].imm.value.u != 0) {
+ cmdIds.push_back(static_cast(instr.Operands[1].imm.value.u));
+ }
});
for (const auto& cmdId : cmdIds)
{
- printf("CmdId: %u\n", cmdId);
- Globals::DynamicCmdIds.insert(cmdId);
+ std::println("AchievementId: {}", cmdId);
+ Globals::AchievementIdSet.insert(static_cast(cmdId));
}
}
- int32_t GetCallCount(uint8_t* target)
+ std::vector GetCalls(uint8_t* target)
{
- size_t sectionSize;
- const auto sectionAddress = GetSection("il2cpp", §ionSize);
- const auto sectionEnd = sectionAddress + sectionSize;
+ 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;
@@ -247,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
@@ -255,7 +261,7 @@ namespace
}
}
- return count;
+ return callSites;
}
uintptr_t FindFunctionEntry(uintptr_t address) // not a correct way to find function entry
@@ -281,14 +287,17 @@ namespace
return address;
}
- uintptr_t Resolve_BitConverter_ToUInt16()
+ void Resolve_BitConverter_ToUInt16()
{
- size_t sectionSize;
- const auto sectionAddress = GetSection("il2cpp", §ionSize);
- const auto sectionEnd = sectionAddress + sectionSize;
+ if (Globals::Offset.BitConverter_ToUInt16 != 0) {
+ Globals::Offset.BitConverter_ToUInt16 += Globals::BaseAddress;
+ return;
+ }
- printf("Section Address: 0x%llX\n", sectionAddress);
- printf("Section End: 0x%llX\n", sectionEnd);
+ const auto il2cppSection = GetSection("il2cpp");
+
+ std::print("Section Address: 0x{:X}", reinterpret_cast(il2cppSection.data()));
+ std::println("Section End: 0x{:X}", reinterpret_cast(il2cppSection.data() + il2cppSection.size()));
/*
mov ecx, 0Fh
@@ -299,8 +308,8 @@ namespace
mov ecx, 5
call ThrowHelper.ThrowArgumentException
*/
- auto candidates = Util::PatternScanAll(sectionAddress, sectionEnd, "B9 0F 00 00 00 E8 ? ? ? ? B9 0E 00 00 00 BA 16 00 00 00 E8 ? ? ? ? B9 05 00 00 00 E8 ? ? ? ?");
- printf("Candidates: %llu\n", candidates.size());
+ auto candidates = Util::PatternScanAll(il2cppSection, "B9 0F 00 00 00 E8 ? ? ? ? B9 0E 00 00 00 BA 16 00 00 00 E8 ? ? ? ? B9 05 00 00 00 E8 ? ? ? ?");
+ std::println("Candidates: {}", candidates.size());
std::vector filteredEntries;
std::ranges::copy_if(candidates, std::back_inserter(filteredEntries), [](uintptr_t& entry) {
@@ -310,19 +319,19 @@ namespace
for (const auto& entry : filteredEntries)
{
- printf("Entry: 0x%llX\n", entry);
+ std::println("Entry: 0x{:X}", entry);
}
- printf("Looking for call counts...\n");
+ std::println("Looking for call counts...");
std::mutex mutex;
std::unordered_map callCounts;
// find the call counts to candidate functions
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);
});
@@ -333,18 +342,181 @@ namespace
uintptr_t targetEntry = 0;
for (const auto& [entry, count] : callCounts)
{
- printf("Entry: 0x%llX, RVA: 0x%08llX, Count: %d\n", entry, entry - Globals::BaseAddress, count);
+ std::println("Entry: 0x{:X}, RVA: 0x{:08X}, Count: {}", entry, entry - Globals::BaseAddress, count);
if (count == 5) {
targetEntry = entry;
}
}
- 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;
+
+ uintptr_t pGetBagManagerByStoreType = candidates.front();
+ {
+ std::println("GetBagManagerByStoreType: 0x{:X}", pGetBagManagerByStoreType);
+
+ const auto isFunctionEntry = [](uintptr_t va) -> bool {
+ auto* code = reinterpret_cast(va);
+ return (va % 16 == 0 &&
+ code[0] == 0x56 && // push rsi
+ code[1] == 0x57); // push rdi
+ };
+
+ auto range = std::views::iota(0, 213);
+ if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pGetBagManagerByStoreType - i); });
+ it != range.end())
+ {
+ pGetBagManagerByStoreType -= *it;
+ }
+ else {
+ std::println("Failed to find function entry");
+ return;
+ }
+
+ std::println("GetBagManagerByStoreType: 0x{:X}", pGetBagManagerByStoreType);
+ }
+
+
+ uintptr_t pOnPlayerStoreNotify = 0;
+ {
+ // get all calls to GetBagManagerByStoreType
+ auto calls = GetCalls(reinterpret_cast(pGetBagManagerByStoreType));
+ auto decodedInstructions = calls | std::views::transform([](auto va) { return DecodeFunction(va); });
+
+ // find the call site with an arbitrary branch (JMP or CALL) after the call
+ auto targetInstructions = std::ranges::find_if(decodedInstructions, [](const auto& 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;
+
+ const auto isFunctionEntry = [](uintptr_t va) -> bool {
+ auto* code = reinterpret_cast(va);
+ return (va % 16 == 0 &&
+ code[0] == 0x56 && // push rsi
+ (*reinterpret_cast(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
+ };
+
+ auto range = std::views::iota(0, 126);
+ if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPlayerStoreNotify - i); });
+ it != range.end())
+ {
+ pOnPlayerStoreNotify -= *it;
+ }
+ else {
+ std::println("Failed to find function entry");
+ return;
+ }
+
+ std::println("OnPlayerStoreNotify: 0x{:X}", pOnPlayerStoreNotify);
+ }
+
+ uintptr_t pOnPacket = 0;
+ {
+ // get all calls to OnPlayerStoreNotify
+ const auto calls = GetCalls(reinterpret_cast(pOnPlayerStoreNotify));
+ if (calls.size() != 1) {
+ std::println("Failed to find call site");
+ return;
+ }
+
+ // ItemModule.OnPacket - search backwards for function entry
+ pOnPacket = calls.front();
+ const auto isFunctionEntry = [](uintptr_t va) -> bool {
+ auto* code = reinterpret_cast(va);
+ return (va % 16 == 0 &&
+ code[0] == 0x56 && // push rsi
+ (*reinterpret_cast(&code[1]) & ~0xFF000000) == _byteswap_ulong(0x4883EC00)); // sub rsp, ??
+ };
+
+ auto range = std::views::iota(0, 3044);
+ if (const auto it = std::ranges::find_if(range, [&](int i) { return isFunctionEntry(pOnPacket - i); });
+ it != range.end())
+ {
+ pOnPacket -= *it;
+ }
+ else {
+ std::println("Failed to find function entry");
+ return;
+ }
+
+ std::println("OnPacket: 0x{:X}", pOnPacket);
+ }
+
+ const auto decodedInstructions = DecodeFunction(pOnPacket);
+ uint32_t cmdid = 0;
+ std::ranges::for_each(decodedInstructions, [&cmdid, pOnPlayerStoreNotify](const DecodedInstruction& i) {
+ static uint32_t immValue = 0; // keep track of the last immediate value
+
+ 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) {
+ uintptr_t branchAddr = Globals::BaseAddress + i.RVA + i.Instruction.length + i.Operands[0].imm.value.s;
+
+ // decode the branch address immediately
+ const auto instructions = DecodeFunction(branchAddr, 10);
+ const auto isMatch = std::ranges::any_of(instructions, [pOnPlayerStoreNotify](const DecodedInstruction& instr) {
+ if (instr.Instruction.mnemonic != ZYDIS_MNEMONIC_CALL)
+ return false;
+
+ uintptr_t destination = 0;
+ ZydisCalcAbsoluteAddress(&instr.Instruction, instr.Operands.data(), Globals::BaseAddress + instr.RVA, &destination);
+ return destination == pOnPlayerStoreNotify;
+ });
+
+ if (isMatch) {
+ cmdid = immValue;
+ }
+
+ }
+ return cmdid == 0; // stop processing if cmdid is found
+ });
+
+ Globals::PlayerStoreId = static_cast(cmdid);
+ std::println("PlayerStoreId: {}", Globals::PlayerStoreId);
}
}
-void InitIL2CPP()
+bool InitIL2CPP()
{
std::string buffer;
buffer.resize(MAX_PATH);
@@ -362,25 +534,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();
- printf("BaseAddress: 0x%llX\n", BaseAddress);
- printf("IsCNREL: %d\n", IsCNREL);
- printf("BitConverter_ToUInt16: 0x%llX\n", Offset.BitConverter_ToUInt16);
+ 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 c043e00..25dad07 100644
--- a/lib/src/il2cpp-types.h
+++ b/lib/src/il2cpp-types.h
@@ -1,5 +1,17 @@
#pragma once
#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
@@ -16,23 +28,49 @@ public:
T* data() {
return vector;
}
+
+ 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];
+ uint16_t m_HeadMagic;
+ uint16_t m_CmdId;
+ uint16_t m_HeaderLength;
+ uint32_t m_DataLength;
+ uint8_t m_Data[1];
+public:
+
+ 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 { m_Data + HeaderLength, DataLength };
+ }
+
+ friend struct PacketMetaStaticAssertHelper;
};
#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
+struct PacketMetaStaticAssertHelper
+{
+ 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
diff --git a/lib/src/util.cpp b/lib/src/util.cpp
index f90a1ec..23b89d4 100644
--- a/lib/src/util.cpp
+++ b/lib/src/util.cpp
@@ -1,5 +1,9 @@
#include
+#include
+#include
+#include
#include "util.h"
+
#include "globals.h"
#ifdef _DEBUG
@@ -11,12 +15,12 @@
namespace
{
struct HandleData {
- DWORD pid;
- HWND hwnd;
+ DWORD Pid;
+ HWND Hwnd;
};
bool IsMainWindow(HWND handle) {
- return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle) == TRUE;
+ return GetWindow(handle, GW_OWNER) == nullptr && IsWindowVisible(handle) == TRUE;
}
bool IsUnityWindow(HWND handle) {
@@ -29,16 +33,16 @@ namespace
HandleData& data = *(HandleData*)lParam;
DWORD pid = 0;
GetWindowThreadProcessId(handle, &pid);
- if (data.pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
+ if (data.Pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle))
return TRUE;
- data.hwnd = handle;
+ data.Hwnd = handle;
return FALSE;
}
- std::tuple, std::vector> PatternToBytes(const char* pattern)
+ std::tuple, std::string> PatternToBytes(const char* pattern)
{
std::vector bytes;
- std::vector maskBytes;
+ std::string mask;
const auto start = const_cast(pattern);
const auto end = const_cast(pattern) + strlen(pattern);
@@ -49,14 +53,14 @@ namespace
if (*current == '?')
++current;
bytes.push_back(-1);
- maskBytes.push_back(false);
+ mask.push_back('?');
}
else {
bytes.push_back(strtoul(current, ¤t, 16));
- maskBytes.push_back(true);
+ mask.push_back('x');
}
}
- return { bytes, maskBytes };
+ return { bytes, mask };
}
}
@@ -69,17 +73,25 @@ namespace Util
{
HWND FindMainWindowByPID(DWORD pid)
{
- HandleData data = { pid, 0 };
+ HandleData data = {
+ .Pid = pid,
+ .Hwnd = nullptr
+ };
EnumWindows(EnumWindowsCallback, (LPARAM)&data);
- return data.hwnd;
+ return data.Hwnd;
}
- std::string Base64Encode(BYTE const* buf, unsigned int bufLen)
+ std::string Base64Encode(std::span data)
+ {
+ return Base64Encode(data.data(), data.size());
+ }
+
+ std::string Base64Encode(uint8_t const* buf, size_t bufLen)
{
std::string ret;
int i = 0;
- BYTE char_array_3[3];
- BYTE char_array_4[4];
+ uint8_t char_array_3[3];
+ uint8_t char_array_4[4];
while (bufLen--) {
char_array_3[i++] = *buf++;
if (i == 3) {
@@ -126,51 +138,67 @@ namespace Util
ErrorDialog("YaeAchievement", msg.c_str());
}
- uintptr_t PatternScan(uintptr_t start, uintptr_t end, const char* pattern)
- {
- const auto [patternBytes, patternMask] = PatternToBytes(pattern);
- const auto scanBytes = reinterpret_cast(start);
-
- const auto patternSize = patternBytes.size();
- const auto pBytes = patternBytes.data();
-
- for (auto i = 0ul; i < end - start - patternSize; ++i) {
- bool found = true;
- for (auto j = 0ul; j < patternSize; ++j) {
- if (scanBytes[i + j] != pBytes[j] && patternMask[j]) {
- found = false;
- break;
- }
- }
- if (found) {
- return reinterpret_cast(&scanBytes[i]);
- }
- }
-
- return 0;
- }
-
- std::vector PatternScanAll(uintptr_t start, uintptr_t end, const char* pattern)
+ std::vector PatternScanAll(std::span bytes, const char* pattern)
{
std::vector results;
const auto [patternBytes, patternMask] = PatternToBytes(pattern);
- const auto scanBytes = reinterpret_cast(start);
+ constexpr std::size_t chunkSize = 16;
- const auto patternSize = patternBytes.size();
- const auto pBytes = patternBytes.data();
+ const auto maskCount = static_cast(std::ceil(patternMask.size() / chunkSize));
+ std::array masks{};
- for (auto i = 0ul; i < end - start - patternSize; ++i) {
- bool found = true;
- for (auto j = 0ul; j < patternSize; ++j) {
- if (scanBytes[i + j] != pBytes[j] && patternMask[j]) {
- found = false;
- break;
+ auto chunks = patternMask | std::views::chunk(chunkSize);
+ for (std::size_t i = 0; auto chunk : chunks) {
+ int32_t mask = 0;
+ for (std::size_t j = 0; j < chunk.size(); ++j) {
+ if (chunk[j] == 'x') {
+ mask |= 1 << j;
}
}
- if (found) {
- results.push_back(reinterpret_cast(&scanBytes[i]));
- i += patternSize - 1;
+ masks[i++] = mask;
+ }
+
+ __m128i xmm1 = _mm_loadu_si128(reinterpret_cast(patternBytes.data()));
+ __m128i xmm2, xmm3, mask;
+
+ auto pData = bytes.data();
+ const auto end = pData + bytes.size() - patternMask.size();
+
+ while (pData < end)
+ {
+ _mm_prefetch(reinterpret_cast(pData + 64), _MM_HINT_NTA);
+
+ if (patternBytes[0] == pData[0])
+ {
+ xmm2 = _mm_loadu_si128(reinterpret_cast(pData));
+ mask = _mm_cmpeq_epi8(xmm1, xmm2);
+
+ if ((_mm_movemask_epi8(mask) & masks[0]) == masks[0])
+ {
+ bool found = true;
+ for (int i = 1; i < maskCount; ++i)
+ {
+ xmm2 = _mm_loadu_si128(reinterpret_cast(pData + i * chunkSize));
+ xmm3 = _mm_loadu_si128(reinterpret_cast(patternBytes.data() + i * chunkSize));
+ mask = _mm_cmpeq_epi8(xmm2, xmm3);
+ if ((_mm_movemask_epi8(mask) & masks[i]) != masks[i])
+ {
+ found = false;
+ break;
+ }
+ }
+
+
+ if (found) {
+ results.push_back(reinterpret_cast(pData));
+ }
+
+ }
+
+
}
+
+ ++pData;
}
return results;
diff --git a/lib/src/util.h b/lib/src/util.h
index 8346e1b..e60cf32 100644
--- a/lib/src/util.h
+++ b/lib/src/util.h
@@ -3,16 +3,17 @@
#include
#include
#include
+#include
namespace Util
{
HWND FindMainWindowByPID(DWORD pid);
- std::string Base64Encode(BYTE const* buf, unsigned int bufLen);
+ std::string Base64Encode(std::span data);
+ std::string Base64Encode(uint8_t const* buf, size_t bufLen);
void ErrorDialog(LPCSTR title, LPCSTR msg);
void ErrorDialog(LPCSTR msg);
void Win32ErrorDialog(DWORD code, DWORD winerrcode);
- uintptr_t PatternScan(uintptr_t start, uintptr_t end, const char* pattern);
- std::vector PatternScanAll(uintptr_t start, uintptr_t end, const char* pattern);
+ std::vector PatternScanAll(std::span bytes, const char* pattern);
}
\ No newline at end of file