From e9ace26d69347fe56e16fe50fb9f31cdc7896b97 Mon Sep 17 00:00:00 2001 From: REL <25654009+34736384@users.noreply.github.com> Date: Thu, 10 Oct 2024 05:06:57 -0400 Subject: [PATCH] refactor --- lib/YaeAchievementLib.vcxproj | 32 ++---- lib/src/dllmain.cpp | 211 ++++++++++++++++++++++++---------- lib/src/globals.h | 28 +++++ lib/src/il2cpp-init.cpp | 32 +++++- lib/src/il2cpp-types.h | 47 ++++++-- lib/src/util.cpp | 168 ++++++++++++++------------- lib/src/util.h | 32 ++---- 7 files changed, 348 insertions(+), 202 deletions(-) create mode 100644 lib/src/globals.h diff --git a/lib/YaeAchievementLib.vcxproj b/lib/YaeAchievementLib.vcxproj index 39b04e4..c1faaf6 100644 --- a/lib/YaeAchievementLib.vcxproj +++ b/lib/YaeAchievementLib.vcxproj @@ -20,9 +20,9 @@ DynamicLibrary - true v143 MultiByte + true DynamicLibrary @@ -30,7 +30,6 @@ v143 true MultiByte - false @@ -45,31 +44,28 @@ - true $(SolutionDir)build\$(Platform)\$(Configuration)\ build\$(Platform)\$(Configuration)\ YaeLib + false - false $(SolutionDir)build\$(Platform)\$(Configuration)\ build\$(Platform)\$(Configuration)\ + false Level3 true _DEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) - true - NotUsing - pch.h + false stdcpplatest - stdc17 + true - Windows - true - false + NotSet + DebugFull copy $(TargetPath) $(ProjectDir)..\bin\Debug\net6.0 @@ -80,29 +76,25 @@ Level3 true true - true _AMD64_;NDEBUG;YAEACHIEVEMENTLIB_EXPORTS;_WINDOWS;_USRDLL;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions) - true - NotUsing - pch.h + false stdcpplatest - stdc17 - None true Speed + true - Windows + NotSet true true - true - false + DebugFull copy $(TargetPath) $(ProjectDir)..\bin\Debug\net8.0-windows\win-x64\YaeAchievementLib.dll /y + diff --git a/lib/src/dllmain.cpp b/lib/src/dllmain.cpp index a23bc4a..6ee7a12 100644 --- a/lib/src/dllmain.cpp +++ b/lib/src/dllmain.cpp @@ -1,87 +1,174 @@ -// ReSharper disable CppCStyleCast -// ReSharper disable CppInconsistentNaming -// ReSharper disable CppClangTidyModernizeUseStdPrint -// ReSharper disable CppClangTidyClangDiagnosticCastAlign -// ReSharper disable CppClangTidyHicppMultiwayPathsCovered -// ReSharper disable CppDefaultCaseNotHandledInSwitchStatement -// ReSharper disable CppClangTidyClangDiagnosticCastFunctionTypeStrict - +// ReSharper disable CppClangTidyCertErr33C #include #include +#include +#include "globals.h" #include "util.h" #include "il2cpp-init.h" #include "il2cpp-types.h" -using Genshin::ByteArray; - -HWND unityWnd = nullptr; -HANDLE hPipe = nullptr; - -void* baClass; -std::string checksum; +CRITICAL_SECTION CriticalSection; +void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index = 0); namespace Hook { - ByteArray* UnityEngine_RecordUserData(const INT type) { - /*if (type == 0) { - const auto len = checksum.length(); - const auto arr = Genshin::il2cpp_array_new_specific(baClass, len); - memcpy(&arr->vector[0], checksum.data(), len); - return arr; - } - return Genshin::il2cpp_array_new_specific(baClass, 0);*/ - return {}; - } - - uint16_t BitConverter_ToUInt16(ByteArray* val, const int startIndex) { - /*const auto ret = CALL_ORIGIN(BitConverter_ToUInt16, val, startIndex); - if (ret == 0xAB89 && ReadMapped(val->vector, 2) == 24082) { - const auto headLength = ReadMapped(val->vector, 4); - const auto dataLength = ReadMapped(val->vector, 6); - const auto cStr = base64_encode(val->vector + 10 + headLength, dataLength) + "\n"; - WriteFile(hPipe, cStr.c_str(), (DWORD) cStr.length(), nullptr, nullptr); - CloseHandle(hPipe); + + uint16_t __fastcall BitConverter_ToUInt16(Array* val, const int startIndex) + { + using namespace Globals; + const auto ToUInt16 = reinterpret_cast(Offset.BitConverter_ToUInt16); + + EnterCriticalSection(&CriticalSection); + SetBreakpoint((HANDLE)-2, 0, false); + const auto ret = ToUInt16(val, startIndex); + SetBreakpoint((HANDLE)-2, Offset.BitConverter_ToUInt16, true); + LeaveCriticalSection(&CriticalSection); + + const auto packet = reinterpret_cast(val->data()); + + using namespace Globals; + if (ret == 0xAB89 && _byteswap_ushort(packet->CmdId) == CmdId) + { + const auto headLength = _byteswap_ushort(packet->HeaderLength); + const auto dataLength = _byteswap_ulong(packet->DataLength); + + const auto base64 = Util::Base64Encode(packet->Data + headLength, dataLength) + "\n"; + +#ifdef _DEBUG + printf("Base64: %s\n", base64.c_str()); + system("pause"); +#endif + + WriteFile(MessagePipe, base64.c_str(), (DWORD)base64.length(), nullptr, nullptr); + CloseHandle(MessagePipe); ExitProcess(0); } - return ret;*/ - return {}; + + return ret; } } -void Run(HMODULE* phModule) { - //AllocConsole(); - //freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); - while ((unityWnd = FindMainWindowByPID(GetCurrentProcessId())) == nullptr) { - Sleep(1000); +LONG __stdcall VectoredExceptionHandler(PEXCEPTION_POINTERS ep) +{ + using namespace Globals; + const auto exceptionRecord = ep->ExceptionRecord; + const auto contextRecord = ep->ContextRecord; + + if (exceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) + { + if (exceptionRecord->ExceptionAddress != reinterpret_cast(Offset.BitConverter_ToUInt16)) { + return EXCEPTION_CONTINUE_SEARCH; + } + + contextRecord->Rip = reinterpret_cast(Hook::BitConverter_ToUInt16); + contextRecord->EFlags &= ~0x100; // clear the trap flag + return EXCEPTION_CONTINUE_EXECUTION; } - Sleep(5000); - DisableVMProtect(); - InitIL2CPP(); - /*for (int i = 0; i < 3; i++) { - const auto result = Genshin::RecordUserData(i); - checksum += string(reinterpret_cast(&result->vector[0]), result->max_length); - baClass = result->klass; + + return EXCEPTION_CONTINUE_SEARCH; +} + +void SetBreakpoint(HANDLE thread, uintptr_t address, bool enable, uint8_t index) +{ + using namespace Globals; + + if (index > 3) { + return; } - HookManager::install(Genshin::RecordUserData, Hook::UnityEngine_RecordUserData); - HookManager::install(Genshin::BitConverter_ToUInt16, Hook::BitConverter_ToUInt16);*/ - hPipe = CreateFile(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); - if (hPipe == INVALID_HANDLE_VALUE) { - Win32ErrorDialog(1001); + + if (!BaseAddress || Offset.BitConverter_ToUInt16 <= BaseAddress) { + // not initialized yet + return; + } + + CONTEXT ctx{}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + GetThreadContext(thread, &ctx); + + DWORD64* dr = &ctx.Dr0; + dr[index] = enable ? address : 0; + + const auto mask = 1ull << (index * 2); + ctx.Dr7 |= mask; + + SetThreadContext(thread, &ctx); +} + +DWORD __stdcall ThreadProc(LPVOID hInstance) +{ +#ifdef _DEBUG + AllocConsole(); + freopen_s((FILE**)stdout, "CONOUT$", "w", stdout); +#endif + InitializeCriticalSection(&CriticalSection); + + const auto hInitThread = CreateThread(nullptr, 0, reinterpret_cast(InitIL2CPP), nullptr, 0, + nullptr); + + using namespace Globals; + const auto pid = GetCurrentProcessId(); + + while ((GameWindow = Util::FindMainWindowByPID(pid)) == nullptr) { + SwitchToThread(); + } + + if (!hInitThread) { + InitIL2CPP(); + } + else { + WaitForSingleObject(hInitThread, INFINITE); + CloseHandle(hInitThread); + } + + MessagePipe = CreateFileA(R"(\\.\pipe\YaeAchievementPipe)", GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + if (MessagePipe == INVALID_HANDLE_VALUE) + { +#ifdef _DEBUG + Util::ErrorDialog("Failed to open pipe"); +#else + Util::Win32ErrorDialog(1001, GetLastError()); ExitProcess(0); +#endif } + + AddVectoredExceptionHandler(1, VectoredExceptionHandler); + while (true) + { + THREADENTRY32 te32{}; + te32.dwSize = sizeof(THREADENTRY32); + const auto hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + for (Thread32First(hSnapshot, &te32); Thread32Next(hSnapshot, &te32);) + { + if (te32.th32OwnerProcessID != pid) { + continue; + } + + if (const auto hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID)) + { + EnterCriticalSection(&CriticalSection); + SetBreakpoint(hThread, Offset.BitConverter_ToUInt16, true); + CloseHandle(hThread); + LeaveCriticalSection(&CriticalSection); + } + } + CloseHandle(hSnapshot); + Sleep(1); + } + + return 0; } // DLL entry point -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReasonForCall, LPVOID lpReserved) { - switch (ulReasonForCall) { - case DLL_PROCESS_ATTACH: - CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Run, new HMODULE(hModule), 0, NULL); - break; - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - break; +BOOL __stdcall DllMain(HMODULE hInstance, DWORD fdwReason, LPVOID lpReserved) +{ + + if (fdwReason == DLL_PROCESS_ATTACH) + { + if (const auto hThread = CreateThread(nullptr, 0, ThreadProc, hInstance, 0, nullptr)) { + CloseHandle(hThread); + } } + return TRUE; } diff --git a/lib/src/globals.h b/lib/src/globals.h new file mode 100644 index 0000000..89af9d4 --- /dev/null +++ b/lib/src/globals.h @@ -0,0 +1,28 @@ +#pragma once +#include + +#define PROPERTY2(type, name, cn, os) \ + type name##_cn = cn; \ + type name##_os = os; \ + type get_##name() { return Globals::IsCNREL ? name##_cn : name##_os; } \ + void set_##name(type value) { if (Globals::IsCNREL) name##_cn = value; else name##_os = value; } \ + __declspec(property(get = get_##name, put = set_##name)) type name; + +namespace Globals +{ + inline HWND GameWindow = nullptr; + inline HANDLE MessagePipe = nullptr; + inline bool IsCNREL = true; + inline uintptr_t BaseAddress = 0; + + // 5.1.0 - 24082 + inline uint16_t CmdId = 24082; // use non-zero to override dynamic search + + class Offsets + { + public: + PROPERTY2(uintptr_t, BitConverter_ToUInt16, 0x0F826CF0, 0x0F825F10); // use non-zero to override dynamic search + }; + + inline Offsets Offset; +} \ No newline at end of file diff --git a/lib/src/il2cpp-init.cpp b/lib/src/il2cpp-init.cpp index f34e05c..facb984 100644 --- a/lib/src/il2cpp-init.cpp +++ b/lib/src/il2cpp-init.cpp @@ -1,12 +1,32 @@ -// ReSharper disable CppCStyleCast -// ReSharper disable CppInconsistentNaming -// ReSharper disable CppClangTidyBugproneMacroParentheses -// ReSharper disable CppClangTidyClangDiagnosticCastAlign - #include #include +#include "globals.h" void InitIL2CPP() { - + std::string buffer; + buffer.resize(MAX_PATH); + ZeroMemory(buffer.data(), MAX_PATH); + const auto pathLength = GetModuleFileNameA(nullptr, buffer.data(), MAX_PATH); + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + buffer.resize(pathLength); + ZeroMemory(buffer.data(), pathLength); + GetModuleFileNameA(nullptr, buffer.data(), pathLength); + } + buffer.shrink_to_fit(); + + using namespace Globals; + IsCNREL = buffer.find("YuanShen.exe") != std::string::npos; + BaseAddress = (uintptr_t)GetModuleHandleA(nullptr); + + if (Offset.BitConverter_ToUInt16 != 0) { + Offset.BitConverter_ToUInt16 += BaseAddress; + } + +#ifdef _DEBUG + printf("BaseAddress: 0x%llX\n", BaseAddress); + printf("IsCNREL: %d\n", IsCNREL); + printf("BitConverter_ToUInt16: 0x%llX\n", Offset.BitConverter_ToUInt16); +#endif } diff --git a/lib/src/il2cpp-types.h b/lib/src/il2cpp-types.h index 0eb9ee7..c043e00 100644 --- a/lib/src/il2cpp-types.h +++ b/lib/src/il2cpp-types.h @@ -1,15 +1,38 @@ -// ReSharper disable CppClangTidyClangDiagnosticReservedIdentifier -// ReSharper disable CppClangTidyBugproneReservedIdentifier - #pragma once +#include -namespace Genshin { +template +class Array +{ +public: + void* klass; + void* monitor; + void* bounds; + size_t max_length; + T vector[1]; - struct ByteArray { - void* klass; - void* monitor; - void* bounds; - uint64_t max_length; - uint8_t vector[32]; - }; -} + Array() = delete; + + T* data() { + return 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 +{ + uint16_t HeadMagic; + uint16_t CmdId; + uint16_t HeaderLength; + uint32_t DataLength; + uint8_t Data[1]; +}; +#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 diff --git a/lib/src/util.cpp b/lib/src/util.cpp index b82eb75..59a0d92 100644 --- a/lib/src/util.cpp +++ b/lib/src/util.cpp @@ -1,95 +1,99 @@ -#include #include - #include "util.h" - -VOID DisableVMProtect() { - DWORD oldProtect = 0; - auto ntdll = GetModuleHandleA("ntdll.dll"); - auto pNtProtectVirtualMemory = GetProcAddress(ntdll, "NtProtectVirtualMemory"); - auto pNtQuerySection = GetProcAddress(ntdll, "NtQuerySection"); - DWORD old; - VirtualProtect(pNtProtectVirtualMemory, 1, PAGE_EXECUTE_READWRITE, &old); - *(uintptr_t*)pNtProtectVirtualMemory = *(uintptr_t*)pNtQuerySection & ~(0xFFui64 << 32) | (uintptr_t)(*(uint32_t*)((uintptr_t)pNtQuerySection + 4) - 1) << 32; - VirtualProtect(pNtProtectVirtualMemory, 1, old, &old); -} - -#pragma region ByteUtils - -bool IsLittleEndian() { - UINT i = 1; - char* c = (char*)&i; - return *c; -} - -#pragma endregion +#include "globals.h" #pragma region FindMainWindowByPID -struct HandleData { - DWORD pid; - HWND hwnd; -}; +namespace +{ + struct HandleData { + DWORD pid; + HWND hwnd; + }; -BOOL IsMainWindow(HWND handle) { - return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle) == TRUE; -} + bool IsMainWindow(HWND handle) { + return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle) == TRUE; + } -BOOL IsUnityWindow(HWND handle) { - TCHAR name[256]; - GetClassName(handle, name, 256); - return _strcmpi(name, "UnityWndClass") == 0; -} + bool IsUnityWindow(HWND handle) { + char szName[256]{}; + GetClassNameA(handle, szName, 256); + return _stricmp(szName, "UnityWndClass") == 0; + } -BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) { - HandleData& data = *(HandleData*)lParam; - DWORD pid = 0; - GetWindowThreadProcessId(handle, &pid); - if (data.pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle)) - return TRUE; - data.hwnd = handle; - return FALSE; -} - -HWND FindMainWindowByPID(DWORD pid) { - HandleData data = { pid, 0 }; - EnumWindows(EnumWindowsCallback, (LPARAM)&data); - return data.hwnd; + BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam) { + HandleData& data = *(HandleData*)lParam; + DWORD pid = 0; + GetWindowThreadProcessId(handle, &pid); + if (data.pid != pid || !IsMainWindow(handle) || !IsUnityWindow(handle)) + return TRUE; + data.hwnd = handle; + return FALSE; + } } #pragma endregion -static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static constexpr LPCSTR base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -std::string base64_encode(BYTE const* buf, unsigned int bufLen) { - std::string ret; - int i = 0; - BYTE char_array_3[3]; - BYTE char_array_4[4]; - while (bufLen--) { - char_array_3[i++] = *buf++; - if (i == 3) { - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - for (i = 0; (i < 4); i++) - ret += base64_chars[char_array_4[i]]; - i = 0; - } - } - if (i) { - int j; - for (j = i; j < 3; j++) - char_array_3[j] = '\0'; - char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; - char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); - char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); - char_array_4[3] = char_array_3[2] & 0x3f; - for (j = 0; j < i + 1; j++) - ret += base64_chars[char_array_4[j]]; - while (i++ < 3) - ret += '='; - } - return ret; -} +namespace Util +{ + HWND FindMainWindowByPID(DWORD pid) + { + HandleData data = { pid, 0 }; + EnumWindows(EnumWindowsCallback, (LPARAM)&data); + return data.hwnd; + } + + std::string Base64Encode(BYTE const* buf, unsigned int bufLen) + { + std::string ret; + int i = 0; + BYTE char_array_3[3]; + BYTE char_array_4[4]; + while (bufLen--) { + char_array_3[i++] = *buf++; + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + for (i = 0; (i < 4); i++) + ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + if (i) { + int j; + for (j = i; j < 3; j++) + char_array_3[j] = '\0'; + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + for (j = 0; j < i + 1; j++) + ret += base64_chars[char_array_4[j]]; + while (i++ < 3) + ret += '='; + } + return ret; + } + + void ErrorDialog(LPCSTR title, LPCSTR msg) + { + MessageBoxA(Globals::GameWindow, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL); + } + + void ErrorDialog(LPCSTR msg) + { + ErrorDialog("YaeAchievement", msg); + } + + void Win32ErrorDialog(DWORD code, DWORD winerrcode) + { + const std::string msg = "CRITICAL ERROR!\nError code: " + std::to_string(winerrcode) + "-" + std::to_string(code) + + "\n\nPlease take the screenshot and contact developer by GitHub Issue to solve this problem\nNOT MIHOYO/COGNOSPHERE CUSTOMER SERVICE!"; + + ErrorDialog("YaeAchievement", msg.c_str()); + } +} \ No newline at end of file diff --git a/lib/src/util.h b/lib/src/util.h index 5458f4d..66bd7c0 100644 --- a/lib/src/util.h +++ b/lib/src/util.h @@ -1,25 +1,17 @@ +// ReSharper disable CppClangTidyClangDiagnosticLanguageExtensionToken #pragma once +#include +#include -using std::string; -VOID DisableVMProtect(); -bool IsLittleEndian(); -HWND FindMainWindowByPID(DWORD pid); -std::string base64_encode(BYTE const* buf, unsigned int bufLen); +namespace Util +{ + HWND FindMainWindowByPID(DWORD pid); + std::string Base64Encode(BYTE const* buf, unsigned int bufLen); -#define ErrorDialogT(title, msg) MessageBox(unityWnd, msg, title, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL) -#define ErrorDialog(msg) ErrorDialogT("YaeAchievement", msg) -#define Win32ErrorDialog(code) ErrorDialogT("YaeAchievement", ("CRITICAL ERROR!\nError code: " + std::to_string(GetLastError()) + "-"#code"\n\nPlease take the screenshot and contact developer by GitHub Issue to solve this problem\nNOT MIHOYO/COGNOSPHERE CUSTOMER SERVICE!").c_str()) + void ErrorDialog(LPCSTR title, LPCSTR msg); + void ErrorDialog(LPCSTR msg); + void Win32ErrorDialog(DWORD code, DWORD winerrcode); -template -static T ReadMapped(void* data, int offset, bool littleEndian = false) { - char* cData = (char*)data; - T result = {}; - if (IsLittleEndian() != littleEndian) { - for (int i = 0; i < sizeof(T); i++) - ((char*)&result)[i] = cData[offset + sizeof(T) - i - 1]; - return result; - } - memcpy(&result, cData + offset, sizeof(result)); - return result; -} + +} \ No newline at end of file