added PlayerStoreNotify

This commit is contained in:
REL
2025-01-07 23:36:27 -05:00
parent e56a6228aa
commit e65f046520
7 changed files with 355 additions and 76 deletions

38
lib/src/NamedPipe.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <Windows.h>
#include <span>
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<DWORD>(size), &bytesWritten, nullptr) || bytesWritten != size)
return false;
return true;
}
bool Write(std::span<const uint8_t> data) const
{
return Write(data.data(), data.size());
}
template <typename T>
bool Write(const T& data) const
{
return Write(&data, sizeof(T));
}
};

View File

@@ -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<PacketMeta*>(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<PacketMeta*>();
const auto packetType = GetPacketType(packet);
if (packetType == PacketType::None)
return ret;
if (dataLength < 500) {
return false;
}
#ifdef _DEBUG
std::println("PacketType: {}", static_cast<uint8_t>(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());

View File

@@ -1,6 +1,7 @@
#pragma once
#include <Windows.h>
#include <unordered_set>
#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<uint16_t> DynamicCmdIds;
inline uint16_t AchievementId = 0; // use non-zero to override dynamic search
inline std::unordered_set<uint16_t> 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
{

View File

@@ -68,8 +68,9 @@ namespace
/// decodes all instruction until next push, ignores branching
/// </summary>
/// <param name="address"></param>
/// <param name="maxInstructions"></param>
/// <returns>std::vector DecodedInstruction</returns>
std::vector<DecodedInstruction> DecodeFunction(uintptr_t address)
std::vector<DecodedInstruction> 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<uintptr_t>(il2cppSection.data()));
@@ -178,7 +186,7 @@ namespace
std::vector<std::vector<DecodedInstruction>> 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<DecodedInstruction>& 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<uint16_t>(cmdId));
std::println("AchievementId: {}", cmdId);
Globals::AchievementIdSet.insert(static_cast<uint16_t>(cmdId));
}
}
int32_t GetCallCount(uint8_t* target)
std::vector<uintptr_t> GetCalls(uint8_t* target)
{
const auto il2cppSection = GetSection("il2cpp");
const auto sectionAddress = reinterpret_cast<uintptr_t>(il2cppSection.data());
const auto sectionSize = il2cppSection.size();
int32_t count = 0;
std::vector<uintptr_t> 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<uintptr_t>(il2cppSection.data()));
@@ -316,9 +329,9 @@ namespace
std::vector<std::future<void>> 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<uintptr_t>(il2cppSection.data()));
std::println("Section End: 0x{:X}", reinterpret_cast<uintptr_t>(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<uint8_t*>(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<DecodedInstruction>& 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<uint8_t*>(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<uint8_t*>(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<uint32_t, uintptr_t> immBranch; // <imm, branch address>
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<uint32_t>(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<uint16_t>(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<void> resolveFuncFuture = std::async(std::launch::async, [] {
if (Offset.BitConverter_ToUInt16 != 0) {
Offset.BitConverter_ToUInt16 += BaseAddress;
}
else {
Offset.BitConverter_ToUInt16 = Resolve_BitConverter_ToUInt16();
}
});
std::future<void> resolveCmdIdFuture = std::async(std::launch::async, [] {
if (CmdId == 0) {
ResolveCmdId();
}
});
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> 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;
}

View File

@@ -1,4 +1,4 @@
#pragma once
// IL2CPP application initializer
void InitIL2CPP();
bool InitIL2CPP();

View File

@@ -2,6 +2,17 @@
#include <cstdint>
#include <span>
#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 <typename T>
class Array
{
@@ -21,27 +32,41 @@ public:
std::span<T> AsSpan() {
return { vector, max_length };
}
template <typename U>
U As() {
return reinterpret_cast<U>(vector);
}
};
static_assert(alignof(Array<uint8_t>) == 8, "Array alignment is incorrect");
static_assert(offsetof(Array<uint8_t>, 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<uint8_t> 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");
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");