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