From 1905edfcc10a49ecba503d7b7ddf8d93aef2490b Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Sat, 22 Nov 2025 18:29:51 +0800 Subject: [PATCH 1/9] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Unbox=20mono/il2cpp=20?= =?UTF-8?q?=E5=86=99=E5=8F=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- UnityResolve.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/UnityResolve.hpp b/UnityResolve.hpp index c66e1b4..48dc4f9 100644 --- a/UnityResolve.hpp +++ b/UnityResolve.hpp @@ -364,9 +364,9 @@ class UnityResolve final { template T Unbox(void *obj) { if (mode_ == Mode::Il2Cpp) { - return static_cast(Invoke("mono_object_unbox", obj)); - } else { return static_cast(Invoke("il2cpp_object_unbox", obj)); + } else { + return static_cast(Invoke("mono_object_unbox", obj)); } } }; @@ -3391,3 +3391,4 @@ class UnityResolve final { inline static void *pDomain{}; }; #endif // UNITYRESOLVE_HPPs + From 4594662a073e9250012e66517dde43fcd5183549 Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Mon, 1 Dec 2025 01:02:06 +0800 Subject: [PATCH 2/9] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加自动搜索GameObjectManager,一键获取所有组件等功能 --- UnityResolve.GOM.hpp | 459 ++++++++++++++++++++++++++++++++++++++++++ UnityResolve.hpp | 25 ++- UnityResolve_Usage.md | 355 ++++++++++++++++++++++++++++++++ 3 files changed, 837 insertions(+), 2 deletions(-) create mode 100644 UnityResolve.GOM.hpp create mode 100644 UnityResolve_Usage.md diff --git a/UnityResolve.GOM.hpp b/UnityResolve.GOM.hpp new file mode 100644 index 0000000..fd2323f --- /dev/null +++ b/UnityResolve.GOM.hpp @@ -0,0 +1,459 @@ +#pragma once + +#if WINDOWS_MODE + +#include +#include +#pragma comment(lib, "Psapi.lib") +#include +#include + +struct UnityGOMInfo { + std::uintptr_t address; + std::uintptr_t offset; +}; + +namespace UnityResolveGOM { + +namespace { + constexpr std::size_t kUnityFunctionScanLen = 0x400; +} + +struct ModuleRange { + std::uintptr_t base; + std::uintptr_t end; +}; + +inline bool GetModuleRange(const wchar_t* name, ModuleRange& range) { + HMODULE hMod = GetModuleHandleW(name); + if (!hMod) return false; + + MODULEINFO mi{}; + if (!GetModuleInformation(GetCurrentProcess(), hMod, &mi, sizeof(mi))) return false; + + range.base = reinterpret_cast(mi.lpBaseOfDll); + range.end = range.base + static_cast(mi.SizeOfImage); + return true; +} + +inline bool IsInModule(const ModuleRange& range, std::uintptr_t addr) { + return addr >= range.base && addr < range.end; +} + +inline std::uintptr_t FindFirstMovInFunction(std::uintptr_t func, std::size_t maxLen, + const ModuleRange& unity) { + const auto* code = reinterpret_cast(func); + + for (std::size_t i = 0; i + 7 <= maxLen; ++i) { + const unsigned char* p = code + i; + + if (p[0] == 0x48 && p[1] == 0x8B) { + unsigned char modrm = p[2]; + if ((modrm & 0xC7) == 0x05) { + auto disp = *reinterpret_cast(p + 3); + auto target = reinterpret_cast(p + 7) + static_cast(disp); + if (IsInModule(unity, target)) { + return target; + } + } + } + } + + return 0; +} + +inline void FindUnityGlobalAndCallOrder(std::uintptr_t func, std::size_t maxLen, const ModuleRange& unity, + std::uintptr_t& unityGlobal, std::uintptr_t& firstCallTarget, + std::size_t& globalPos, std::size_t& callPos) { + const auto* code = reinterpret_cast(func); + unityGlobal = 0; + firstCallTarget = 0; + globalPos = static_cast(-1); + callPos = static_cast(-1); + + for (std::size_t i = 0; i + 7 <= maxLen; ++i) { + const unsigned char* p = code + i; + + // mov r64, [rip+disp32] + if (p[0] == 0x48 && p[1] == 0x8B && globalPos == static_cast(-1)) { + unsigned char modrm = p[2]; + if ((modrm & 0xC7) == 0x05) { + auto disp = *reinterpret_cast(p + 3); + auto target = reinterpret_cast(p + 7) + static_cast(disp); + if (IsInModule(unity, target)) { + unityGlobal = target; + globalPos = i; + } + } + } + + // mov rax, [abs] + if (i + 10 <= maxLen && p[0] == 0x48 && p[1] == 0xA1 && globalPos == static_cast(-1)) { + auto target = *reinterpret_cast(p + 2); + if (IsInModule(unity, target)) { + unityGlobal = target; + globalPos = i; + } + } + + // call rel32 -> only accept UnityPlayer targets + if (p[0] == 0xE8 && callPos == static_cast(-1)) { + auto rel = *reinterpret_cast(p + 1); + auto tgt = reinterpret_cast(p + 5) + static_cast(rel); + if (IsInModule(unity, tgt)) { + firstCallTarget = tgt; + callPos = i; + } + } + + if (globalPos != static_cast(-1) && callPos != static_cast(-1)) + break; + } +} + +inline std::uintptr_t FindFirstCallToUnityPlayer(std::uintptr_t func, std::size_t maxLen, + const ModuleRange& unityPlayer) { + const auto* code = reinterpret_cast(func); + + for (std::size_t i = 0; i + 5 <= maxLen; ++i) { + const unsigned char* p = code + i; + if (p[0] == 0xE8) { + auto rel = *reinterpret_cast(p + 1); + auto tgt = reinterpret_cast(p + 5) + static_cast(rel); + if (IsInModule(unityPlayer, tgt)) { + return tgt; + } + } + } + return 0; +} + +inline std::uintptr_t ResolveGOMFromUnityPlayerEntry(std::uintptr_t entryFunc, + const ModuleRange& unityPlayer) { + std::uintptr_t entryGlobal = 0; + std::uintptr_t entryCall = 0; + std::size_t gpos = static_cast(-1); + std::size_t cpos = static_cast(-1); + FindUnityGlobalAndCallOrder(entryFunc, kUnityFunctionScanLen, unityPlayer, entryGlobal, entryCall, gpos, cpos); + + // mov in entry comes first -> this global is GOM + if (gpos != static_cast(-1) && (cpos == static_cast(-1) || gpos < cpos)) { + return entryGlobal; + } + + // call in entry comes first -> jump once and search first mov in that function + if (cpos != static_cast(-1) && (gpos == static_cast(-1) || cpos < gpos)) { + std::uintptr_t foundGlobal = FindFirstMovInFunction(entryCall, kUnityFunctionScanLen, unityPlayer); + if (foundGlobal) { + return foundGlobal; + } + } + + return 0; +} + +inline std::uintptr_t FindFirstMovToModule(std::uintptr_t func, std::size_t maxLen, + const ModuleRange& targetModule) { + const auto* code = reinterpret_cast(func); + + for (std::size_t i = 0; i + 7 <= maxLen; ++i) { + const unsigned char* p = code + i; + + if (p[0] == 0x48 && p[1] == 0x8B && p[2] == 0x05) { + auto disp = *reinterpret_cast(p + 3); + auto target = reinterpret_cast(p + 7) + static_cast(disp); + if (IsInModule(targetModule, target)) { + __try { + return *reinterpret_cast(target); + } __except(EXCEPTION_EXECUTE_HANDLER) { + return 0; + } + } + } + + if (i + 10 <= maxLen && p[0] == 0x48 && p[1] == 0xA1) { + auto target = *reinterpret_cast(p + 2); + if (IsInModule(targetModule, target)) { + __try { + return *reinterpret_cast(target); + } __except(EXCEPTION_EXECUTE_HANDLER) { + return 0; + } + } + } + + if (i + 10 <= maxLen && p[0] == 0x48 && p[1] == 0xB8) { + auto target = *reinterpret_cast(p + 2); + if (IsInModule(targetModule, target)) { + return target; + } + } + } + + return 0; +} + +inline UnityGOMInfo FindGameObjectManagerImpl() { + UnityGOMInfo info{0, 0}; + + HMODULE il2cppModule = GetModuleHandleW(L"GameAssembly.dll"); + HMODULE monoModule = nullptr; + if (!il2cppModule) { + monoModule = GetModuleHandleW(L"mono-2.0-bdwgc.dll"); + if (!monoModule) monoModule = GetModuleHandleW(L"mono-2.0-sgen.dll"); + if (!monoModule) monoModule = GetModuleHandleW(L"mono.dll"); + if (!monoModule) monoModule = GetModuleHandleW(L"mono-2.0.dll"); + } + + if (!monoModule && !il2cppModule) return info; + + bool isIl2cpp = (il2cppModule != nullptr); + void* runtimeModule = isIl2cpp ? static_cast(il2cppModule) : static_cast(monoModule); + + std::cout << "Runtime: " << (isIl2cpp ? "IL2CPP" : "MONO") << std::endl; + + UnityResolve::Init(runtimeModule, isIl2cpp ? UnityResolve::Mode::Il2Cpp : UnityResolve::Mode::Mono); + UnityResolve::ThreadAttach(); + + auto coreAssembly = UnityResolve::Get("UnityEngine.CoreModule.dll"); + if (!coreAssembly) return info; + + auto cameraClass = coreAssembly->Get("Camera"); + if (!cameraClass) return info; + auto getMainMethod = cameraClass->Get("get_main"); + if (!getMainMethod) return info; + + auto nativePtr = getMainMethod->Cast(); + if (!nativePtr) return info; + std::uintptr_t getMainAddr = reinterpret_cast(nativePtr); + + // Call get_main once to ensure initialization + getMainMethod->Invoke(nullptr); + + ModuleRange unityPlayer{}; + if (!GetModuleRange(L"UnityPlayer.dll", unityPlayer)) return info; + + std::uintptr_t unityEntry = 0; + if (isIl2cpp) { + ModuleRange gameAssembly{}; + if (!GetModuleRange(L"GameAssembly.dll", gameAssembly)) return info; + unityEntry = FindFirstMovToModule(getMainAddr, kUnityFunctionScanLen, gameAssembly); + if (unityEntry && !IsInModule(unityPlayer, unityEntry)) unityEntry = 0; + if (!unityEntry) { + unityEntry = FindFirstMovToModule(getMainAddr, kUnityFunctionScanLen, unityPlayer); + } + } else { + unityEntry = FindFirstMovToModule(getMainAddr, kUnityFunctionScanLen, unityPlayer); + } + + if (!unityEntry) return info; + + std::cout << "Entry[0]->UnityPlayer.dll+0x" + << std::hex << std::uppercase << (unityEntry - unityPlayer.base) + << std::dec << std::nouppercase << std::endl; + + std::uintptr_t step2Func = FindFirstCallToUnityPlayer(unityEntry, kUnityFunctionScanLen, unityPlayer); + if (!step2Func) return info; + + std::cout << "Entry[1]->UnityPlayer.dll+0x" + << std::hex << std::uppercase << (step2Func - unityPlayer.base) + << std::dec << std::nouppercase << std::endl; + + std::uintptr_t unityGlobal = ResolveGOMFromUnityPlayerEntry(step2Func, unityPlayer); + if (!unityGlobal) return info; + + info.address = unityGlobal; + info.offset = unityGlobal - unityPlayer.base; + std::cout << "GameObjectManager->UnityPlayer.dll+0x" << std::hex << std::uppercase << info.offset + << std::dec << std::nouppercase << std::endl; + return info; +} + +inline UnityGOMInfo FindGameObjectManager() { + static bool initialized = false; + static UnityGOMInfo cached{0, 0}; + + if (initialized) { + return cached; + } + + __try { + cached = FindGameObjectManagerImpl(); + } __except (EXCEPTION_EXECUTE_HANDLER) { + cached = UnityGOMInfo{0, 0}; + } + + initialized = true; + return cached; +} + +inline std::uintptr_t SafeReadPtr(std::uintptr_t addr) { + __try { + return *reinterpret_cast(addr); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return 0; + } +} + +inline std::int32_t SafeReadInt32(std::uintptr_t addr) { + __try { + return *reinterpret_cast(addr); + } __except (EXCEPTION_EXECUTE_HANDLER) { + return 0; + } +} + +struct GameObjectInfo { + std::uintptr_t nativeObject; + UnityResolve::UnityType::GameObject* managedObject; +}; + +inline std::vector EnumerateGameObjectsFromManager(std::uintptr_t manager) { + std::vector result; + if (!manager) return result; + + std::uintptr_t listHead = SafeReadPtr(manager + 0x28); + if (!listHead) return result; + + const std::size_t kMaxObjects = 1000000; + std::uintptr_t node = listHead; + + for (std::size_t i = 0; node && i < kMaxObjects; ++i) { + std::uintptr_t nativeObject = SafeReadPtr(node + 0x10); + std::uintptr_t managedObject = 0; + std::uintptr_t next = SafeReadPtr(node + 0x8); + + if (nativeObject) { + managedObject = SafeReadPtr(nativeObject + 0x28); + } + + if (managedObject) { + auto managed = reinterpret_cast(managedObject); + result.push_back(GameObjectInfo{nativeObject, managed}); + } + + if (!next || next == listHead) { + break; + } + + node = next; + } + + return result; +} + +inline std::vector EnumerateGameObjects() { + UnityGOMInfo info = FindGameObjectManager(); + if (!info.address) return {}; + + std::uintptr_t manager = SafeReadPtr(info.address); + if (!manager) return {}; + + return EnumerateGameObjectsFromManager(manager); +} + +struct ComponentInfo { + std::uintptr_t nativeComponent; + UnityResolve::UnityType::Component* managedComponent; +}; + +inline std::vector EnumerateComponents() { + std::vector result; + + auto gameObjects = EnumerateGameObjects(); + if (gameObjects.empty()) return result; + + const int kMaxComponentsPerObject = 1024; + + for (const auto &info : gameObjects) { + if (!info.nativeObject) { + continue; + } + + auto nativeGo = info.nativeObject; + + auto componentPool = SafeReadPtr(nativeGo + 0x30); + if (!componentPool) { + continue; + } + + auto componentCount = SafeReadInt32(nativeGo + 0x38); + if (componentCount <= 0 || componentCount > kMaxComponentsPerObject) { + continue; + } + + for (int i = 0; i < componentCount; ++i) { + auto slotAddr = componentPool + 0x8 + static_cast(i) * 0x10; + auto compNative = SafeReadPtr(slotAddr); + if (!compNative) { + continue; + } + + auto managedComp = SafeReadPtr(compNative + 0x28); + if (!managedComp) { + continue; + } + + auto managed = reinterpret_cast(managedComp); + result.push_back(ComponentInfo{compNative, managed}); + } + } + + return result; +} + +inline std::vector EnumerateComponentsByGetComponents() { + std::vector result; + + auto gomGameObjects = EnumerateGameObjects(); + if (gomGameObjects.empty()) return result; + + // 收集 GOM 中所有原生 GameObject 指针 + std::vector gomNativeGos; + gomNativeGos.reserve(gomGameObjects.size()); + for (const auto &info : gomGameObjects) { + if (info.nativeObject) { + gomNativeGos.push_back(info.nativeObject); + } + } + if (gomNativeGos.empty()) return result; + + auto coreAssembly = UnityResolve::Get("UnityEngine.CoreModule.dll"); + if (!coreAssembly) return result; + + auto gameObjectClass = coreAssembly->Get("GameObject"); + auto componentClass = coreAssembly->Get("Component"); + if (!gameObjectClass || !componentClass) return result; + + // 通过托管 API 获取所有 GameObject,再用 native 指针与 GOM 结果匹配 + auto managedGos = gameObjectClass->FindObjectsByType(); + for (auto go : managedGos) { + if (!go) continue; + + auto nativeGo = SafeReadPtr(reinterpret_cast(go) + 0x10); + if (!nativeGo) continue; + + bool inGom = false; + for (auto ng : gomNativeGos) { + if (ng == nativeGo) { + inGom = true; + break; + } + } + if (!inGom) continue; + + auto comps = go->GetComponents(componentClass); + for (auto comp : comps) { + if (!comp) continue; + + std::uintptr_t nativeComp = SafeReadPtr(reinterpret_cast(comp) + 0x10); + result.push_back(ComponentInfo{nativeComp, comp}); + } + } + + return result; +} + +} // namespace UnityResolveGOM + +#endif // WINDOWS_MODE diff --git a/UnityResolve.hpp b/UnityResolve.hpp index 48dc4f9..4443369 100644 --- a/UnityResolve.hpp +++ b/UnityResolve.hpp @@ -1531,6 +1531,23 @@ class UnityResolve final { } }; + struct Il2CppClassInternal { + void *reserved0; + void *reserved1; + const char *name; + const char *namespaze; + }; + + struct MonoClassInternal { + char padding[0x48]; + const char *name; + const char *namespaze; + }; + + struct MonoVTableInternal { + MonoClassInternal *klass; + }; + enum class BindingFlags : uint32_t { Default = 0, IgnoreCase = 1, @@ -3389,6 +3406,10 @@ class UnityResolve final { inline static void *hmodule_; inline static std::unordered_map address_{}; inline static void *pDomain{}; -}; -#endif // UNITYRESOLVE_HPPs + }; + +#if WINDOWS_MODE +#include "UnityResolve.GOM.hpp" +#endif +#endif // UNITYRESOLVE_HPP diff --git a/UnityResolve_Usage.md b/UnityResolve_Usage.md new file mode 100644 index 0000000..c931fc8 --- /dev/null +++ b/UnityResolve_Usage.md @@ -0,0 +1,355 @@ +# UnityResolve 支持库用法总结 + +> 本文说明 `UnityResolve.hpp` 与 `UnityResolve.GOM.hpp` 的核心用法,便于在任意 Unity/Mono/Il2Cpp 游戏中快速集成。 + +--- + +## 1. UnityResolve.hpp 基本概念 + +### 1.1 初始化与运行模式 + +- **运行模式**: + - `UnityResolve::Mode::Il2Cpp` + - `UnityResolve::Mode::Mono` +- **初始化入口**: + - `UnityResolve::Init(void* hmodule, UnityResolve::Mode mode)` + - `hmodule`: + - Il2Cpp:`HMODULE gameAsm = GetModuleHandleW(L"GameAssembly.dll");` + - Mono:`HMODULE mono = GetModuleHandleW(L"mono-2.0-bdwgc.dll");` 等 + +示例: + +```cpp +HMODULE hGameAsm = GetModuleHandleW(L"GameAssembly.dll"); +if (hGameAsm) { + UnityResolve::Init(hGameAsm, UnityResolve::Mode::Il2Cpp); +} else { + HMODULE hMono = GetModuleHandleW(L"mono.dll"); + if (hMono) UnityResolve::Init(hMono, UnityResolve::Mode::Mono); +} +``` + +### 1.2 线程附加 / 分离 + +- 在非 Unity 主线程中调用托管 API 时,需要先附加线程: + - `UnityResolve::ThreadAttach();` +- 结束时可选调用: + - `UnityResolve::ThreadDetach();` + +通常做法: + +```cpp +DWORD WINAPI WorkerThread(LPVOID) { + UnityResolve::ThreadAttach(); + // 托管调用逻辑 + UnityResolve::ThreadDetach(); + return 0; +} +``` + +### 1.3 程序集与类查询 + +- 全局程序集列表:`UnityResolve::assembly`(`std::vector`) +- 按名称获取程序集: + +```cpp +UnityResolve::Assembly* core = UnityResolve::Get("UnityEngine.CoreModule.dll"); +UnityResolve::Assembly* game = UnityResolve::Get("BlueArchive.dll"); +``` + +- 在程序集内查找类: + +```cpp +using UR = UnityResolve; +UR::Assembly* asmBA = UR::Get("BlueArchive.dll"); +if (asmBA) { + // 按类名 + 命名空间 + 父类名(后两个可用 "*" 通配) + UR::Class* battle = asmBA->Get("Battle", "MX.Logic.Battles"); +} +``` + +### 1.4 字段与方法查询 + +在 `UnityResolve::Class` 上提供统一的 `Get` 接口: + +- 获取字段对象:`Field*`: + +```cpp +UnityResolve::Field* fld = battle->Get("playerGroup"); +int32_t offset = fld ? fld->offset : 0; +``` + +- 直接按字段名获取偏移:`int32_t*`: + +```cpp +int32_t* pOffset = battle->Get("playerGroup"); +int32_t offset = pOffset ? *pOffset : 0; +``` + +- 获取方法:`Method*`,可带参数类型过滤: + +```cpp +// 按方法名查找(不区分重载) +UnityResolve::Method* m1 = battle->Get("Tick"); + +// 带参数类型过滤(参数列表长度与类型名匹配) +UnityResolve::Method* m2 = battle->Get( + "DoSomething", + {"System.Int32", "System.String"} +); +``` + +### 1.5 字段读写辅助 + +`Class` 提供了简化的实例字段读写封装: + +```cpp +// 按字段名读写 +int v = battle->GetValue(obj, "hp"); +battle->SetValue(obj, "hp", 9999); + +// 按偏移读写 +int v2 = battle->GetValue(obj, offset); +battle->SetValue(obj, offset, 123); +``` + +### 1.6 方法调用(Invoke / RuntimeInvoke) + +#### 1.6.1 直接函数指针调用:`Method::Invoke` + +适用于:方法已经编译成 native(Il2Cpp 默认如此,Mono 需 `Compile()`)。 + +```cpp +UnityResolve::Method* method = battle->Get("get_PlayerCount"); +if (method) { + int count = method->Invoke(instance /*this*/); +} +``` + +#### 1.6.2 runtime 调用:`Method::RuntimeInvoke` + +封装了 `il2cpp_runtime_invoke` / `mono_runtime_invoke`: + +```cpp +UnityResolve::Method* method = battle->Get("Tick"); +if (method) { + method->RuntimeInvoke(instance /*this*/); +} +``` + +### 1.7 UnityType 辅助结构 + +`UnityResolve::UnityType` 内提供常用 Unity 结构体 / 类的 C++ 映射和静态方法,例如: + +- 向量与矩阵:`Vector2`/`Vector3`/`Vector4`/`Quaternion`/`Matrix4x4` +- UnityEngine 对象包装:`GameObject`/`Transform`/`Camera`/`Rigidbody` 等 +- 集合类型:`UnityType::Array` / `List` / `Dictionary` + +示例:获取所有摄像机: + +```cpp +using UR = UnityResolve; +std::vector cams = UR::UnityType::Camera::GetAllCamera(); +``` + +示例:通过 `GameObject` 获取组件: + +```cpp +using UR = UnityResolve; +UR::UnityType::GameObject* go = UR::UnityType::GameObject::Find("Player"); +if (go) { + UR::UnityType::Transform* tr = go->GetTransform(); +} +``` + +--- + +## 2. UnityResolve.GOM.hpp:GOM 探测 + +`UnityResolve.GOM.hpp` 提供自动定位 Unity `GameObjectManager` 全局指针的封装 API,无需手动维护偏移表。 + +### 2.1 平台要求 + +- **仅 Windows**:依赖 `WINDOWS_MODE` 和 `Psapi.h` +- **运行时支持**:Mono 和 IL2CPP +- **前置条件**: + - `UnityPlayer.dll` 已加载 + - `UnityEngine.CoreModule.dll` 和 `Camera.get_main` 可访问 + +### 2.2 核心 API + +```cpp +struct UnityGOMInfo { + std::uintptr_t address; // GOM 全局变量的绝对地址 + std::uintptr_t offset; // 相对 UnityPlayer.dll 的偏移 +}; + +UnityGOMInfo UnityResolveGOM::FindGameObjectManager(); +``` + +**返回值**: +- `address == 0`:扫描失败 +- `address != 0`:成功,`offset` 可用于跨版本适配 + +### 2.3 内部原理概览(可选阅读) + +内部实现大致会完成以下几步(仅作概念说明,具体指令匹配与偏移细节以源码为准): + +1. **检测运行时类型** + - Il2Cpp:优先检测 `GameAssembly.dll` + - Mono:在一组常见 mono 模块名中查找已加载模块 + +2. **获取扫描起点** + - 使用 `UnityResolve::Init` + `UnityResolve::ThreadAttach` 完成托管运行时初始化 + - 通过反射拿到 `UnityEngine.Camera.get_main` 的 native 函数指针,作为入口起点 + +3. **沿调用链解析 GOM 全局指针** + - 在入口附近跟踪进入 `UnityPlayer.dll` 的调用 + - 在 UnityPlayer 的相关函数中,解析指向全局区域的访问,并识别出 `GameObjectManager` 全局变量 + +运行时会输出简单日志,示例: + +```text +Runtime: IL2CPP +GameObjectManager->UnityPlayer.dll+0x1D15C78 +``` + +### 2.4 使用示例 + +```cpp +#include +#include +#include "UnityResolve.hpp" + +DWORD WINAPI MainThread(LPVOID param) { + HMODULE hModule = static_cast(param); + AllocConsole(); + FILE* fp; + freopen_s(&fp, "CONOUT$", "w", stdout); + + std::cout << "[UnityCatcher] Installation Successful" << std::endl; + + UnityGOMInfo gom = UnityResolveGOM::FindGameObjectManager(); + if (!gom.address) { + std::cout << "GameObjectManager not found" << std::endl; + } + + std::cout << "Exiting..." << std::endl; + FreeConsole(); + FreeLibraryAndExitThread(hModule, 0); + return 0; +} + +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID) { + if (ul_reason_for_call == DLL_PROCESS_ATTACH) { + DisableThreadLibraryCalls(hModule); + HANDLE hThread = CreateThread(nullptr, 0, MainThread, hModule, 0, nullptr); + if (hThread) CloseHandle(hThread); + } + return TRUE; +} +``` + +**注意**:`FindGameObjectManager()` 在 Debug/开发阶段会打印少量扫描日志(运行时类型、GOM 偏移等),Release 版可按需关闭这些输出。 + +--- + +## 3. 基于 GOM 的遍历接口 + +### 3.1 GameObject 遍历 + +`UnityResolve.GOM.hpp` 内提供对 GOM 链表的统一封装: + +```cpp +struct GameObjectInfo { + std::uintptr_t nativeObject; // 原生 GameObject 指针 + UnityResolve::UnityType::GameObject* managedObject; // 托管 GameObject 实例 +}; + +std::vector UnityResolveGOM::EnumerateGameObjects(); +``` + +- 返回值包含 GOM 管理的所有 GameObject(过滤掉空指针)。 +- 需要在 `FindGameObjectManager()` 成功之后使用。 + +### 3.2 Component 遍历(原生组件池) + +```cpp +struct ComponentInfo { + std::uintptr_t nativeComponent; // 原生 Component 指针 + UnityResolve::UnityType::Component* managedComponent; // 托管 Component 实例 +}; + +std::vector UnityResolveGOM::EnumerateComponents(); +``` + +- 直接通过原生 `GameObject` 的组件池遍历组件。 +- 适合需要同时关注 native/managed 指针结构的场景。 + +### 3.3 Component 遍历(推荐:托管 GetComponents) + +```cpp +std::vector UnityResolveGOM::EnumerateComponentsByGetComponents(); +``` + +- 使用托管 `GameObject.GetComponents` 统一获取组件列表,然后为每个组件补充原生指针。 +- 只保留 GOM 中存在的 GameObject 对应的组件,更接近 Unity“逻辑视角”的组件集合。 +- 推荐用于**统计 / 打印所有组件类型**等用途。 + +### 3.4 统计与打印示例 + +下例展示如何统计并打印所有组件(示例逻辑与 `example_UnityCatcher.cpp` 保持一致,只保留核心部分): + +```cpp +using UR = UnityResolve; + +// 1. 自动定位 GOM +UnityGOMInfo gom = UnityResolveGOM::FindGameObjectManager(); +if (!gom.address) { + std::cout << "GameObjectManager not found" << std::endl; + return; +} + +// 2. 遍历 GameObject 和 Component +auto gameObjects = UnityResolveGOM::EnumerateGameObjects(); +auto components = UnityResolveGOM::EnumerateComponentsByGetComponents(); + +std::cout << "GameObjects: " << gameObjects.size() << std::endl; +std::cout << "Components: " << components.size() << std::endl; + +// 3. 打印每个组件的原生地址与完整类型名 +for (const auto &c : components) { + if (!c.managedComponent) continue; + + auto type = c.managedComponent->GetType(); + if (!type) continue; + + const char* ns = type->GetNamespace(); + const char* name = type->GetName(); + + std::cout << std::hex << std::uppercase + << "0x" << c.nativeComponent << "-" + << (ns && *ns ? ns : "None") << "." << (name ? name : "") + << std::dec << std::nouppercase + << std::endl; +} +``` + +--- + +## 4. 集成建议 + +1. **一次性初始化**: + - `UnityResolve::Init` + `ThreadAttach` 只需在进程启动时调用一次 + - `FindGameObjectManager()` 在启动阶段调用一次,缓存 `offset` + +2. **性能优化**: + - 避免在高频逻辑中重复调用 GOM 扫描 + - 将 `offset` 保存为全局常量,用于后续 GOM 遍历 + +3. **架构分层**: + - **UnityResolve**:处理反射(类/字段/方法)和托管调用 + - **GOM 扫描**:一次性定位 GameObjectManager 地址 + - **业务逻辑**:使用 GOM 地址遍历场景对象 + +这样实现"通用框架 + 游戏业务"解耦,提升跨版本适配能力. From 53e9c588ba431e1a8efef73dd60e9b76997d53ef Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Sun, 30 Nov 2025 17:11:12 +0000 Subject: [PATCH 3/9] docs: add GOM support section to README --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index f44f34f..3cc65bc 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,45 @@

功能使用 (How to use)


+**GOM 支持 (GameObject Manager Support)** + +> **说明** +> +> 新增 `UnityResolve.GOM.hpp`(仅在 `WINDOWS_MODE` 下包含),用于通过 UnityPlayer 中的全局指针解析并枚举原生的 GameObject / Component 列表(基于原生内存读取)。此模块在某些场景下能补充托管 API 的不足,例如需要直接从原生层索引对象或在 IL2CPP 环境下结合托管对象做匹配。 + +- **平台**: 仅 Windows(`WINDOWS_MODE`)。 +- **要求**: 编译器需开启 SEH(结构化异常处理),否则读取内存时可能导致异常。 +- **主要 API 与类型**: + - `UnityResolveGOM::FindGameObjectManager()` - 查找 GOM 全局指针并返回地址信息 + - `UnityResolveGOM::EnumerateGameObjects()` - 枚举返回 `std::vector`,包含 `nativeObject` 与 `managedObject` + - `UnityResolveGOM::EnumerateComponents()` - 枚举返回 `std::vector`,包含 `nativeComponent` 与 `managedComponent` + - `UnityResolveGOM::EnumerateComponentsByGetComponents()` - 通过托管 API 获取 GameObject 并匹配 GOM 的原生指针 + +**示例用法** +```c++ +#include "UnityResolve.hpp" +#if WINDOWS_MODE +#include "UnityResolve.GOM.hpp" +#endif + +// 初始化 UnityResolve(示例) +UnityResolve::Init(GetModuleHandle(L"GameAssembly.dll | mono.dll"), UnityResolve::Mode::Mono); +UnityResolve::ThreadAttach(); + +auto gos = UnityResolveGOM::EnumerateGameObjects(); +for (const auto &g : gos) { + // g.nativeObject 是原生指针,g.managedObject 是托管的 GameObject* +} + +auto comps = UnityResolveGOM::EnumerateComponents(); +for (const auto &c : comps) { + // c.nativeComponent / c.managedComponent +} +``` + +**注意**: 该模块通过在 UnityPlayer 模块中查找全局指针并直接读取内存实现,对不同 Unity 版本或自定义引擎导出可能存在兼容性差异,如发生异常或数据不一致,请提交 Issue 并提供 Unity 版本与相关模块信息。 + + #### 使用GLM (use glm) > [!CAUTION] > 新版本强制性要求 \ From be84950316b348ab4422bf18a9a5676f949de355 Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Mon, 8 Dec 2025 13:09:51 +0800 Subject: [PATCH 4/9] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加GOM/GameObject/Component/Managed结构定义 --- UnityResolve.hpp | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/UnityResolve.hpp b/UnityResolve.hpp index 4443369..dc4ae6b 100644 --- a/UnityResolve.hpp +++ b/UnityResolve.hpp @@ -1548,6 +1548,90 @@ class UnityResolve final { MonoClassInternal *klass; }; + struct Il2CppGameObjectNode { + Il2CppGameObjectNode *pLast; + Il2CppGameObjectNode *pNext; + void *nativeObject; + }; + + struct Il2CppGameObjectManager { + char pad_0000[0x28]; + Il2CppGameObjectNode *firstNode; + }; + + struct Il2CppNativeGameObject { + char pad_0000[0x28]; + void *managedObject; + void *componentPool; + int componentCount; + char pad_003C[0x4]; + }; + + struct Il2CppComponentPool { + char pad_0000[0x8]; + void *firstComponent; + }; + + struct Il2CppNativeComponent { + char pad_0000[0x28]; + void *managedComponent; + }; + + struct Il2CppManagedObjectHeader { + void *klass; + void *monitor; + }; + + struct Il2CppManagedGameObject : Il2CppManagedObjectHeader { + void *nativeObject; + }; + + struct Il2CppManagedComponent : Il2CppManagedObjectHeader { + void *nativeObject; + }; + + struct MonoGameObjectNode { + MonoGameObjectNode *pLast; + MonoGameObjectNode *pNext; + void *nativeObject; + }; + + struct MonoGameObjectManager { + char pad_0000[0x28]; + MonoGameObjectNode *firstNode; + }; + + struct MonoNativeGameObject { + char pad_0000[0x28]; + void *managedObject; + void *componentPool; + int componentCount; + char pad_003C[0x4]; + }; + + struct MonoComponentPool { + char pad_0000[0x8]; + void *firstComponent; + }; + + struct MonoNativeComponent { + char pad_0000[0x28]; + void *managedComponent; + }; + + struct MonoManagedObjectHeader { + MonoVTableInternal *vtable; + void *monitor; + }; + + struct MonoManagedGameObject : MonoManagedObjectHeader { + void *nativeObject; + }; + + struct MonoManagedComponent : MonoManagedObjectHeader { + void *nativeObject; + }; + enum class BindingFlags : uint32_t { Default = 0, IgnoreCase = 1, From bacb0b85e0a169504090b32d668851e69aa8d045 Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Thu, 11 Dec 2025 01:05:02 +0800 Subject: [PATCH 5/9] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 外部支持、结构修复 --- ...5\345\255\230\347\273\223\346\236\204.txt" | 57 ++ ...5\345\255\230\347\273\223\346\236\204.txt" | 61 +++ .../UnityExternalTransform_PosAlgorithm.txt | 100 ++++ External/Camera/UnityExternalCamera.hpp | 144 +++++ .../Camera/UnityExternalWorldToScreen.hpp | 90 ++++ External/Core/UnityExternalMemory.hpp | 57 ++ External/Core/UnityExternalMemoryConfig.hpp | 42 ++ External/Core/UnityExternalTypes.hpp | 152 ++++++ External/ExternalResolve.hpp | 22 + .../Managed/ManagedObject.hpp | 68 +++ .../Native/NativeComponent.hpp | 44 ++ .../Native/NativeGameObject.hpp | 80 +++ .../Native/NativeTransform.hpp | 233 ++++++++ .../GameObjectManager/UnityExternalGOM.hpp | 175 ++++++ .../MemoryRead/UnityExternalMemoryWinAPI.hpp | 62 +++ README.md | 496 ++++++++---------- UnityResolve.GOM.hpp | 2 +- UnityResolve.hpp | 8 +- 18 files changed, 1623 insertions(+), 270 deletions(-) create mode 100644 "External/Analysis/IL2CPP\345\206\205\345\255\230\347\273\223\346\236\204.txt" create mode 100644 "External/Analysis/MONO\345\206\205\345\255\230\347\273\223\346\236\204.txt" create mode 100644 External/Analysis/UnityExternalTransform_PosAlgorithm.txt create mode 100644 External/Camera/UnityExternalCamera.hpp create mode 100644 External/Camera/UnityExternalWorldToScreen.hpp create mode 100644 External/Core/UnityExternalMemory.hpp create mode 100644 External/Core/UnityExternalMemoryConfig.hpp create mode 100644 External/Core/UnityExternalTypes.hpp create mode 100644 External/ExternalResolve.hpp create mode 100644 External/GameObjectManager/Managed/ManagedObject.hpp create mode 100644 External/GameObjectManager/Native/NativeComponent.hpp create mode 100644 External/GameObjectManager/Native/NativeGameObject.hpp create mode 100644 External/GameObjectManager/Native/NativeTransform.hpp create mode 100644 External/GameObjectManager/UnityExternalGOM.hpp create mode 100644 External/MemoryRead/UnityExternalMemoryWinAPI.hpp diff --git "a/External/Analysis/IL2CPP\345\206\205\345\255\230\347\273\223\346\236\204.txt" "b/External/Analysis/IL2CPP\345\206\205\345\255\230\347\273\223\346\236\204.txt" new file mode 100644 index 0000000..06acc31 --- /dev/null +++ "b/External/Analysis/IL2CPP\345\206\205\345\255\230\347\273\223\346\236\204.txt" @@ -0,0 +1,57 @@ +// IL2CPP 运行时 64 位内存布局(GameObjectManager 与 GameObject 相关结构) +// 所有偏移均为字节偏移,从结构体起始地址算起 + +// UnityPlayer.dll + GameObjectManagerOffset -> GameObjectManager +struct Il2CppGameObjectNode { + Il2CppGameObjectNode* pLast; // 0x00 前一个节点 + Il2CppGameObjectNode* pNext; // 0x08 后一个节点 + void* nativeObject; // 0x10 原生 GameObject 指针 -> Il2CppNativeGameObject* +}; + +struct Il2CppGameObjectManager { + char pad_0000[0x28]; // 0x00 保留 + Il2CppGameObjectNode* firstNode; // 0x28 链表头节点 +}; + +struct Il2CppNativeGameObject { + char pad_0000[0x28]; // 0x00 保留 + void* managedObject; // 0x28 托管对象指针 -> Il2CppManagedGameObject* + void* componentPool; // 0x30 组件池指针 -> Il2CppComponentPool* + char pad_0038[0x8]; // 0x38 保留 + int componentCount; // 0x40 组件数量 + char pad_0044[0x1C]; // 0x44 保留 + void* objectName; // 0x60 名称字符串指针 +}; + +struct Il2CppComponentPool { + char pad_0000[0x8]; // 0x00 保留 + void* firstComponent; // 0x08 第一个组件指针 -> Il2CppNativeComponent* + // 后续组件地址:0x18, 0x28, ... 步长 0x10 +}; + +struct Il2CppNativeComponent { + char pad_0000[0x28]; // 0x00 保留 + void* managedComponent; // 0x28 托管组件指针 -> Il2CppManagedComponent* +}; + +// IL2CPP 托管对象头部(Il2CppObject) +struct Il2CppManagedObjectHeader { + void* klass; // 0x00 类元数据指针 -> Il2CppClassInternal* + void* monitor; // 0x08 同步锁 +}; + +struct Il2CppManagedGameObject : Il2CppManagedObjectHeader { + void* nativeObject; // 0x10 原生对象指针 -> Il2CppNativeGameObject* +}; + +struct Il2CppManagedComponent : Il2CppManagedObjectHeader { + void* nativeObject; // 0x10 原生组件指针 -> Il2CppNativeComponent* +}; + +// IL2CPP 类元数据布局(用于获取类型信息) +struct Il2CppClassInternal { + void* reserved0; // 0x00 保留 + void* reserved1; // 0x08 保留 + const char* name; // 0x10 类名 + const char* namespaze; // 0x18 命名空间 +}; \ No newline at end of file diff --git "a/External/Analysis/MONO\345\206\205\345\255\230\347\273\223\346\236\204.txt" "b/External/Analysis/MONO\345\206\205\345\255\230\347\273\223\346\236\204.txt" new file mode 100644 index 0000000..fe9a9cf --- /dev/null +++ "b/External/Analysis/MONO\345\206\205\345\255\230\347\273\223\346\236\204.txt" @@ -0,0 +1,61 @@ +// Mono 运行时 64 位内存布局(GameObjectManager 与 GameObject 相关结构) +// 所有偏移均为字节偏移,从结构体起始地址算起 + +// UnityPlayer.dll + GameObjectManagerOffset -> GameObjectManager +struct MonoGameObjectNode { + MonoGameObjectNode* pLast; // 0x00 前一个节点 + MonoGameObjectNode* pNext; // 0x08 后一个节点 + void* nativeObject; // 0x10 原生 GameObject 指针 -> MonoNativeGameObject* +}; + +struct MonoGameObjectManager { + char pad_0000[0x28]; // 0x00 保留 + MonoGameObjectNode* firstNode; // 0x28 链表头节点 +}; + +struct MonoNativeGameObject { + char pad_0000[0x28]; // 0x00 保留 + void* managedObject; // 0x28 托管对象指针 -> MonoManagedGameObject* + void* componentPool; // 0x30 组件池指针 -> MonoComponentPool* + char pad_0038[0x8]; // 0x38 保留 + int componentCount; // 0x40 组件数量 + char pad_0044[0x1C]; // 0x44 保留 + void* objectName; // 0x60 名称字符串指针 +}; + +struct MonoComponentPool { + char pad_0000[0x8]; // 0x00 保留 + void* firstComponent; // 0x08 第一个组件指针 -> MonoNativeComponent* + // 后续组件地址:0x18, 0x28, ... 步长 0x10 +}; + +struct MonoNativeComponent { + char pad_0000[0x28]; // 0x00 保留 + void* managedComponent; // 0x28 托管组件指针 -> MonoManagedComponent* +}; + +// Mono 托管对象虚表 +struct MonoVTableInternal { + void* klass; // 0x00 类元数据指针 -> MonoClassInternal* +}; + +// Mono 类元数据布局(用于获取类型信息) +struct MonoClassInternal { + char pad_0000[0x48]; // 0x00 保留 + const char* name; // 0x48 类名 + const char* namespaze; // 0x50 命名空间 +}; + +// Mono 托管对象头部 +struct MonoManagedObjectHeader { + MonoVTableInternal* vtable; // 0x00 虚表指针 + void* monitor; // 0x08 同步锁 +}; + +struct MonoManagedGameObject : MonoManagedObjectHeader { + void* nativeObject; // 0x10 原生对象指针 -> MonoNativeGameObject* +}; + +struct MonoManagedComponent : MonoManagedObjectHeader { + void* nativeObject; // 0x10 原生组件指针 -> MonoNativeComponent* +}; \ No newline at end of file diff --git a/External/Analysis/UnityExternalTransform_PosAlgorithm.txt b/External/Analysis/UnityExternalTransform_PosAlgorithm.txt new file mode 100644 index 0000000..1d4a06c --- /dev/null +++ b/External/Analysis/UnityExternalTransform_PosAlgorithm.txt @@ -0,0 +1,100 @@ +Unity Transform 世界坐标外部读取说明 +==================================== + +一、前提 +-------- +- 已经拿到 **Transform 的原生指针**(记为 `T`)。 +- 按 Mono 结构:`T` 是 `MonoNativeComponent*`,其 `+0x28` 指回 managed Transform,但这里不需要用到 managed,只用原生 Transform 本体。 +- 所有内存读取都通过 `ReadProcessMemory`(本工程里由 `UnityExternal::GetTransformWorldPosition` 封装)。 + +二、核心思路 +------------ +Unity 内部并不是直接在 Transform 里存一个世界坐标,而是: +- 把所有 Transform 节点的数据塞到一个连续的数组里(`nodeData`)。 +- 再用一个「父索引数组」`parentIndices` 表示层级结构。 +- `Transform::GetPosition` 做的事情就是: + 1. 找到本节点在数组里的下标 `index`。 + 2. 从 `nodeData[index]` 取到本地坐标/旋转/缩放,作为起点。 + 3. 然后沿着 `parentIndices[index]` 一路往父节点走,每一层用四元数+缩放+平移把向量累积到世界坐标系。 + +`UnityExternalTransform.hpp` 里的实现就是**按 IDA 里 `Transform::GetPosition` 的反编译结果 1:1 翻译过来的**,只是把 Unity 的 `ReadMemory` 换成了跨进程的 `ReadProcessMemory`。 + +三、指针链与关键偏移 +-------------------- +以下偏移都是**针对 Transform 原生指针 `T`**: + +1. 读取层级状态指针 `statePtr` + - 地址:`T + 0x38` + - 类型:`void* statePtr`(内部结构类似 `TransformHierarchyState*`)。 + - 如果读出来是 0,说明这个 Transform 没有有效的层级数据,直接失败。 + +2. 从 `statePtr` 里取出两个数组指针: + - `nodeData = *(statePtr + 0x18)` + - 一个连续数组,每个 Transform 节点占 48 字节(12 个 float)。 + - 可以理解为 `float nodeData[][12]`。 + - `parentIndices = *(statePtr + 0x20)` + - 一个 `int32_t` 数组,保存每个节点的父索引。 + - `parentIndices[i]` 表示第 `i` 个节点的父节点索引,`-1` 表示根(无父)。 + +3. 读取当前 Transform 在数组里的下标 `index` + - 地址:`T + 0x40` + - 类型:`int32_t index`。 + - 如果 `index < 0`,说明无效,直接失败。 + +四、nodeData 每个节点的布局 +---------------------------- +每个节点占 48 字节,即 12 个 float,布局如下(按 IDA 和实际代码): + +- `float t[4] = node[0..3]` → 平移向量(XYZ,带填充) +- `float q[4] = node[4..7]` → 四元数(旋转) +- `float m[4] = node[8..11]` → 某种缩放/矩阵系数(在算法里与当前向量做乘法) + +也就是说: + +- 当前节点在数组里的起始地址: + - `selfAddr = nodeData + index * 48` (因为每个节点 48 字节) +- 当前节点的平移/旋转/缩放: + - `t = *(float4*)(selfAddr + 0x00)` + - `q = *(float4*)(selfAddr + 0x10)` + - `m = *(float4*)(selfAddr + 0x20)` + +五、世界坐标计算流程(概念版) +------------------------------ +1. 初始累积向量: + - 读取当前节点:`self = nodeData[index]`。 + - 把 `self` 的平移向量 `t` 作为起点: + - `acc = t`。 + +2. 找到第一个父节点索引: + - `parent = parentIndices[index]`。 + +3. 循环沿父链向上: + - 条件:`while (parent >= 0)`: + 1. 读取父节点数据: + - `node = nodeData[parent]`(同样 12 个 float)。 + - `t_parent = node[0..3]`,`q_parent = node[4..7]`,`m_parent = node[8..11]`。 + 2. 用父节点的 `q_parent` 和 `m_parent` 对当前 `acc` 做一次旋转/缩放,然后加上 `t_parent`: + - 这一块在 IDA 里是一大坨 SSE `_mm_mul_ps/_mm_add_ps/_mm_shuffle_epi32`, + 在 `UnityExternalTransform.hpp::ComputeWorldPositionFromHierarchy` 里完全照抄。 + - 概念上就是:`acc = RotateScale(q_parent, m_parent, acc) + t_parent`。 + 3. 读取下一个父索引: + - `parent = parentIndices[parent]`。 + +4. 直到 `parent < 0`(到根节点为止),循环结束。 + +5. 此时 `acc` 就是最终的世界坐标向量: + - `x = acc.x` + - `y = acc.y` + - `z = acc.z` + +六、总结 +-------- +只要拿到 Transform 原生指针 `T`,就能按以下固定链条取世界坐标: + - `statePtr = *(T + 0x38)` + - `nodeData = *(statePtr + 0x18)` + - `parentIndices = *(statePtr + 0x20)` + - `index = *(int32*)(T + 0x40)` + - `acc = nodeData[index].t` + - `for (parent = parentIndices[index]; parent >= 0; parent = parentIndices[parent])`: + - `acc = ApplyParentTransform(nodeData[parent], acc)` + - 返回 `acc.xyz` 作为世界坐标。 \ No newline at end of file diff --git a/External/Camera/UnityExternalCamera.hpp b/External/Camera/UnityExternalCamera.hpp new file mode 100644 index 0000000..e85ef28 --- /dev/null +++ b/External/Camera/UnityExternalCamera.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include +#include +#include + +#include "../Core/UnityExternalMemory.hpp" +#include "../Core/UnityExternalMemoryConfig.hpp" +#include "../Core/UnityExternalTypes.hpp" +#include "../GameObjectManager/UnityExternalGOM.hpp" + +#include "glm/glm.hpp" +#include "glm/gtc/type_ptr.hpp" + +namespace UnityExternal { + +// Read camera's view-projection matrix +// nativeCamera + 0x100 -> 4x4 matrix (16 floats) +inline bool Camera_GetMatrix(std::uintptr_t nativeCamera, glm::mat4& outMatrix) +{ + outMatrix = glm::mat4(1.0f); + if (!nativeCamera) { + return false; + } + + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc) { + return false; + } + + float data[16] = {}; + if (!acc->Read(nativeCamera + 0x100u, data, sizeof(data))) { + return false; + } + + outMatrix = glm::make_mat4(data); + return true; +} + +// Find main camera with priority: +// 1. GameObject name "Main Camera" +// 2. GameObject name "Camera Top" +// 3. First enabled Camera component +inline bool FindMainCamera(const GOMWalker& walker, + std::uintptr_t gomGlobalAddress, + std::uintptr_t& outNativeCamera, + std::uintptr_t& outManagedCamera) +{ + outNativeCamera = 0; + outManagedCamera = 0; + + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc || !gomGlobalAddress) { + return false; + } + + std::vector components; + if (!walker.EnumerateComponentsFromGlobal(gomGlobalAddress, components)) { + return false; + } + if (components.empty()) { + return false; + } + + RuntimeKind runtime = walker.GetRuntime(); + + // Candidates + std::uintptr_t mainCameraNative = 0, mainCameraManaged = 0; + std::uintptr_t cameraTopNative = 0, cameraTopManaged = 0; + std::uintptr_t firstEnabledNative = 0, firstEnabledManaged = 0; + + for (const auto& entry : components) { + if (!entry.managedComponent || !entry.nativeComponent) { + continue; + } + + TypeInfo typeInfo; + if (!GetManagedType(runtime, *acc, entry.managedComponent, typeInfo)) { + continue; + } + + if (typeInfo.name != "Camera") { + continue; + } + + // nativeCamera+0x30 -> GameObject native + std::uintptr_t goNative = 0; + if (!ReadPtrGlobal(entry.nativeComponent + 0x30u, goNative) || !goNative) { + continue; + } + + // GameObject+0x60 -> name pointer + std::uintptr_t namePtr = 0; + if (!ReadPtr(*acc, goNative + 0x60u, namePtr) || !namePtr) { + continue; + } + + std::string goName; + if (!ReadCString(*acc, namePtr, goName)) { + continue; + } + + // Priority 1: "Main Camera" + if (goName == "Main Camera" && !mainCameraNative) { + mainCameraNative = entry.nativeComponent; + mainCameraManaged = entry.managedComponent; + break; // Highest priority, return immediately + } + + // Priority 2: "Camera Top" + if (goName == "Camera Top" && !cameraTopNative) { + cameraTopNative = entry.nativeComponent; + cameraTopManaged = entry.managedComponent; + continue; + } + + // Priority 3: First camera (as fallback) + if (!firstEnabledNative) { + firstEnabledNative = entry.nativeComponent; + firstEnabledManaged = entry.managedComponent; + } + } + + // Return by priority + if (mainCameraNative) { + outNativeCamera = mainCameraNative; + outManagedCamera = mainCameraManaged; + return true; + } + if (cameraTopNative) { + outNativeCamera = cameraTopNative; + outManagedCamera = cameraTopManaged; + return true; + } + if (firstEnabledNative) { + outNativeCamera = firstEnabledNative; + outManagedCamera = firstEnabledManaged; + return true; + } + + return false; +} + +} // namespace UnityExternal diff --git a/External/Camera/UnityExternalWorldToScreen.hpp b/External/Camera/UnityExternalWorldToScreen.hpp new file mode 100644 index 0000000..e523dcf --- /dev/null +++ b/External/Camera/UnityExternalWorldToScreen.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include "glm/glm.hpp" + +namespace UnityExternal { + +struct ScreenRect { + float x; + float y; + float width; + float height; +}; + +struct WorldToScreenResult { + bool visible; // true if point is in front of camera and projection succeeded + float x; // screen X in pixels + float y; // screen Y in pixels + float depth; // depth along camera forward (>0 means in front) +}; + +// Simplified: Convert world position to screen position using only the view-projection matrix. +// viewProj : projection * view matrix (from Camera+0x100) +// screen : screen/viewport rectangle (usually 0,0,width,height) +// worldPos : target world position +inline WorldToScreenResult WorldToScreenPoint(const glm::mat4& viewProj, + const ScreenRect& screen, + const glm::vec3& worldPos) +{ + WorldToScreenResult out{}; + out.visible = false; + out.x = 0.0f; + out.y = 0.0f; + out.depth = 0.0f; + + glm::vec4 clip = viewProj * glm::vec4(worldPos, 1.0f); + if (clip.w <= 0.001f) { + return out; + } + + glm::vec3 ndc = glm::vec3(clip) / clip.w; // -1..1 + + // NDC -> screen pixels (Y flipped for screen coordinates) + float sx = (ndc.x + 1.0f) * 0.5f * screen.width + screen.x; + float sy = (1.0f - ndc.y) * 0.5f * screen.height + screen.y; + + out.x = sx; + out.y = sy; + out.depth = clip.w; + out.visible = (ndc.x >= -1.0f && ndc.x <= 1.0f && ndc.y >= -1.0f && ndc.y <= 1.0f); + return out; +} + +// Full version: Convert world position to screen position with explicit camera position/forward. +inline WorldToScreenResult WorldToScreenPointFull(const glm::mat4& viewProj, + const glm::vec3& camPos, + const glm::vec3& camForward, + const ScreenRect& screen, + const glm::vec3& worldPos) +{ + WorldToScreenResult out{}; + out.visible = false; + out.x = 0.0f; + out.y = 0.0f; + out.depth = 0.0f; + + glm::vec4 clip = viewProj * glm::vec4(worldPos, 1.0f); + if (clip.w == 0.0f) { + return out; + } + + glm::vec3 ndc = glm::vec3(clip) / clip.w; // -1..1 + + // NDC -> screen pixels + float sx = (ndc.x + 1.0f) * 0.5f * screen.width + screen.x; + float sy = (ndc.y + 1.0f) * 0.5f * screen.height + screen.y; + + // depth along camera forward + glm::vec3 delta = worldPos - camPos; + float depth = glm::dot(delta, camForward); + + out.x = sx; + out.y = sy; + out.depth = depth; + out.visible = (depth > 0.0f); + return out; +} + +} // namespace UnityExternal diff --git a/External/Core/UnityExternalMemory.hpp b/External/Core/UnityExternalMemory.hpp new file mode 100644 index 0000000..96cda9a --- /dev/null +++ b/External/Core/UnityExternalMemory.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +namespace UnityExternal { + +// Memory accessor interface for cross-process memory reading +class IMemoryAccessor { +public: + virtual ~IMemoryAccessor() = default; + virtual bool Read(std::uintptr_t address, void* buffer, std::size_t size) const = 0; + virtual bool Write(std::uintptr_t address, const void* buffer, std::size_t size) const = 0; +}; + +// Read a typed value from memory +template +inline bool ReadValue(const IMemoryAccessor& mem, std::uintptr_t address, T& out) { + return mem.Read(address, &out, sizeof(T)); +} + +// Read a pointer from memory +inline bool ReadPtr(const IMemoryAccessor& mem, std::uintptr_t address, std::uintptr_t& out) { + return ReadValue(mem, address, out); +} + +// Read a 32-bit integer from memory +inline bool ReadInt32(const IMemoryAccessor& mem, std::uintptr_t address, std::int32_t& out) { + return ReadValue(mem, address, out); +} + +// Read a C-string from memory (with max length limit) +inline bool ReadCString(const IMemoryAccessor& mem, std::uintptr_t address, std::string& out, std::size_t maxLen = 256) { + out.clear(); + if (!address) { + return false; + } + + char buffer[257] = {}; + std::size_t readLen = (maxLen < 256) ? maxLen : 256; + if (!mem.Read(address, buffer, readLen)) { + return false; + } + + buffer[readLen] = '\0'; + out = buffer; + return true; +} + +// Write a typed value to memory +template +inline bool WriteValue(const IMemoryAccessor& mem, std::uintptr_t address, const T& value) { + return mem.Write(address, &value, sizeof(T)); +} + +} // namespace UnityExternal diff --git a/External/Core/UnityExternalMemoryConfig.hpp b/External/Core/UnityExternalMemoryConfig.hpp new file mode 100644 index 0000000..9180dea --- /dev/null +++ b/External/Core/UnityExternalMemoryConfig.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include "UnityExternalMemory.hpp" + +namespace UnityExternal { + +// Global memory accessor singleton +inline const IMemoryAccessor* g_memoryAccessor = nullptr; + +inline void SetGlobalMemoryAccessor(const IMemoryAccessor* accessor) { + g_memoryAccessor = accessor; +} + +inline const IMemoryAccessor* GetGlobalMemoryAccessor() { + return g_memoryAccessor; +} + +// Global helper functions using the global accessor +template +inline bool ReadValueGlobal(std::uintptr_t address, T& out) { + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc) return false; + return ReadValue(*acc, address, out); +} + +template +inline bool WriteValueGlobal(std::uintptr_t address, const T& value) { + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc) return false; + return WriteValue(*acc, address, value); +} + +inline bool ReadPtrGlobal(std::uintptr_t address, std::uintptr_t& out) { + return ReadValueGlobal(address, out); +} + +inline bool ReadInt32Global(std::uintptr_t address, std::int32_t& out) { + return ReadValueGlobal(address, out); +} + +} // namespace UnityExternal diff --git a/External/Core/UnityExternalTypes.hpp b/External/Core/UnityExternalTypes.hpp new file mode 100644 index 0000000..a25854f --- /dev/null +++ b/External/Core/UnityExternalTypes.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include "UnityExternalMemory.hpp" + +namespace UnityExternal { + +// Runtime type enumeration +enum class RuntimeKind : int { + Il2Cpp, + Mono +}; + +// Type information returned by GetManagedType +struct TypeInfo { + std::string name; + std::string namespaze; +}; + +namespace detail { + +// IL2CPP internal structures +struct Il2CppManagedObjectHeader { + std::uintptr_t klass; +}; + +struct Il2CppClassInternal { + unsigned char pad[0x10]; + std::uintptr_t name; + std::uintptr_t namespaze; +}; + +// Mono internal structures +struct MonoManagedObjectHeader { + std::uintptr_t vtable; +}; + +struct MonoVTableInternal { + std::uintptr_t klass; +}; + +struct MonoClassInternal { + unsigned char pad[0x48]; + std::uintptr_t name; + std::uintptr_t namespaze; +}; + +inline bool GetIl2CppType(const IMemoryAccessor& mem, std::uintptr_t managedObj, TypeInfo& out) { + Il2CppManagedObjectHeader header{}; + if (!ReadValue(mem, managedObj, header)) { + return false; + } + if (!header.klass) { + return false; + } + + Il2CppClassInternal klass{}; + if (!ReadValue(mem, header.klass, klass)) { + return false; + } + + std::string name; + std::string ns; + if (!ReadCString(mem, klass.name, name)) { + return false; + } + if (klass.namespaze) { + if (!ReadCString(mem, klass.namespaze, ns)) { + return false; + } + } + + out.name = name; + out.namespaze = ns; + return true; +} + +inline bool GetMonoType(const IMemoryAccessor& mem, std::uintptr_t managedObj, TypeInfo& out) { + MonoManagedObjectHeader header{}; + if (!ReadValue(mem, managedObj, header)) { + return false; + } + if (!header.vtable) { + return false; + } + + MonoVTableInternal vtable{}; + if (!ReadValue(mem, header.vtable, vtable)) { + return false; + } + if (!vtable.klass) { + return false; + } + + MonoClassInternal klass{}; + if (!ReadValue(mem, vtable.klass, klass)) { + return false; + } + + std::string name; + std::string ns; + if (!ReadCString(mem, klass.name, name)) { + return false; + } + if (klass.namespaze) { + if (!ReadCString(mem, klass.namespaze, ns)) { + return false; + } + } + + out.name = name; + out.namespaze = ns; + return true; +} + +} // namespace detail + +// Get managed object's type info (class name + namespace) +inline bool GetManagedType(RuntimeKind runtime, const IMemoryAccessor& mem, std::uintptr_t managedObject, TypeInfo& out) { + if (!managedObject) { + return false; + } + + if (runtime == RuntimeKind::Il2Cpp) { + return detail::GetIl2CppType(mem, managedObject, out); + } + + return detail::GetMonoType(mem, managedObject, out); +} + +// Get only the class name +inline bool GetManagedClassName(RuntimeKind runtime, const IMemoryAccessor& mem, std::uintptr_t managedObject, std::string& out) { + TypeInfo info; + if (!GetManagedType(runtime, mem, managedObject, info)) { + return false; + } + out = info.name; + return true; +} + +// Get only the namespace +inline bool GetManagedNamespace(RuntimeKind runtime, const IMemoryAccessor& mem, std::uintptr_t managedObject, std::string& out) { + TypeInfo info; + if (!GetManagedType(runtime, mem, managedObject, info)) { + return false; + } + out = info.namespaze; + return true; +} + +} // namespace UnityExternal diff --git a/External/ExternalResolve.hpp b/External/ExternalResolve.hpp new file mode 100644 index 0000000..0f8c0fb --- /dev/null +++ b/External/ExternalResolve.hpp @@ -0,0 +1,22 @@ +#pragma once + +// Core +#include "Core/UnityExternalMemory.hpp" +#include "Core/UnityExternalMemoryConfig.hpp" +#include "Core/UnityExternalTypes.hpp" + +// Default memory reader (WinAPI implementation). +// Users can implement their own IMemoryAccessor and call SetGlobalMemoryAccessor +// to plug in driver-based or custom memory backends. +#include "MemoryRead/UnityExternalMemoryWinAPI.hpp" + +// GameObjectManager +#include "GameObjectManager/UnityExternalGOM.hpp" +#include "GameObjectManager/Managed/ManagedObject.hpp" +#include "GameObjectManager/Native/NativeComponent.hpp" +#include "GameObjectManager/Native/NativeGameObject.hpp" +#include "GameObjectManager/Native/NativeTransform.hpp" + +// Camera +#include "Camera/UnityExternalCamera.hpp" +#include "Camera/UnityExternalWorldToScreen.hpp" diff --git a/External/GameObjectManager/Managed/ManagedObject.hpp b/External/GameObjectManager/Managed/ManagedObject.hpp new file mode 100644 index 0000000..90463ee --- /dev/null +++ b/External/GameObjectManager/Managed/ManagedObject.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +#include "../../Core/UnityExternalMemory.hpp" +#include "../../Core/UnityExternalMemoryConfig.hpp" +#include "../../Core/UnityExternalTypes.hpp" + +namespace UnityExternal { + +// Managed object wrapper (works for both Mono and IL2CPP) +// All managed objects share the same structure: +// +0x00 -> vtable (Mono) or klass (IL2CPP) +// +0x10 -> native pointer +struct ManagedObject { + RuntimeKind runtime; + std::uintptr_t address; + + ManagedObject() : runtime(RuntimeKind::Mono), address(0) {} + ManagedObject(RuntimeKind r, std::uintptr_t addr) : runtime(r), address(addr) {} + + bool IsValid() const { return address != 0; } + + // Get native pointer from managed object (+0x10) + bool GetNative(std::uintptr_t& outNative) const { + outNative = 0; + if (!address) return false; + return ReadPtrGlobal(address + 0x10u, outNative) && outNative != 0; + } + + // Get type info (class name + namespace) + bool GetTypeInfo(TypeInfo& out) const { + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc || !address) return false; + return GetManagedType(runtime, *acc, address, out); + } + + // Get class name + std::string GetClassName() const { + TypeInfo info; + if (!GetTypeInfo(info)) return std::string(); + return info.name; + } + + // Get namespace + std::string GetNamespace() const { + TypeInfo info; + if (!GetTypeInfo(info)) return std::string(); + return info.namespaze; + } +}; + +// Helper: Convert managed address to native +inline bool Managed_GetNative(std::uintptr_t managedAddress, std::uintptr_t& outNative) { + outNative = 0; + if (!managedAddress) return false; + return ReadPtrGlobal(managedAddress + 0x10u, outNative) && outNative != 0; +} + +// Helper: Convert native address to managed +inline bool Native_GetManaged(std::uintptr_t nativeAddress, std::uintptr_t& outManaged) { + outManaged = 0; + if (!nativeAddress) return false; + return ReadPtrGlobal(nativeAddress + 0x28u, outManaged) && outManaged != 0; +} + +} // namespace UnityExternal diff --git a/External/GameObjectManager/Native/NativeComponent.hpp b/External/GameObjectManager/Native/NativeComponent.hpp new file mode 100644 index 0000000..f31d269 --- /dev/null +++ b/External/GameObjectManager/Native/NativeComponent.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include "../../Core/UnityExternalMemory.hpp" +#include "../../Core/UnityExternalMemoryConfig.hpp" + +namespace UnityExternal { + +// Native Component layout: +// +0x28 -> managed component pointer +// +0x30 -> native GameObject pointer + +struct NativeComponent { + std::uintptr_t address; + + NativeComponent() : address(0) {} + explicit NativeComponent(std::uintptr_t addr) : address(addr) {} + + bool IsValid() const { return address != 0; } + + // Get managed component pointer (+0x28) + bool GetManaged(std::uintptr_t& outManaged) const { + outManaged = 0; + if (!address) return false; + return ReadPtrGlobal(address + 0x28u, outManaged) && outManaged != 0; + } + + // Get native GameObject pointer (+0x30) + bool GetGameObject(std::uintptr_t& outGameObject) const { + outGameObject = 0; + if (!address) return false; + return ReadPtrGlobal(address + 0x30u, outGameObject) && outGameObject != 0; + } +}; + +// Helper function +inline bool NativeComponent_GetGameObject(std::uintptr_t nativeComponent, std::uintptr_t& outGameObjectNative) { + outGameObjectNative = 0; + if (!nativeComponent) return false; + return ReadPtrGlobal(nativeComponent + 0x30u, outGameObjectNative) && outGameObjectNative != 0; +} + +} // namespace UnityExternal diff --git a/External/GameObjectManager/Native/NativeGameObject.hpp b/External/GameObjectManager/Native/NativeGameObject.hpp new file mode 100644 index 0000000..fa7a9a2 --- /dev/null +++ b/External/GameObjectManager/Native/NativeGameObject.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include "../../Core/UnityExternalMemory.hpp" +#include "../../Core/UnityExternalMemoryConfig.hpp" +#include "../../Core/UnityExternalTypes.hpp" + +namespace UnityExternal { + +// Native GameObject layout: +// +0x28 -> managed GameObject pointer +// +0x30 -> component pool pointer +// +0x40 -> component count (int32) +// +0x60 -> name string pointer + +struct NativeGameObject { + std::uintptr_t address; + + NativeGameObject() : address(0) {} + explicit NativeGameObject(std::uintptr_t addr) : address(addr) {} + + bool IsValid() const { return address != 0; } + + // Get managed pointer (+0x28) + bool GetManaged(std::uintptr_t& outManaged) const { + outManaged = 0; + if (!address) return false; + return ReadPtrGlobal(address + 0x28u, outManaged) && outManaged != 0; + } + + // Get component pool (+0x30) + bool GetComponentPool(std::uintptr_t& outPool) const { + outPool = 0; + if (!address) return false; + return ReadPtrGlobal(address + 0x30u, outPool) && outPool != 0; + } + + // Get component count (+0x40) + bool GetComponentCount(std::int32_t& outCount) const { + outCount = 0; + if (!address) return false; + return ReadInt32Global(address + 0x40u, outCount); + } + + // Get name (+0x60 -> string pointer) + std::string GetName() const { + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc || !address) return std::string(); + + std::uintptr_t namePtr = 0; + if (!ReadPtrGlobal(address + 0x60u, namePtr) || !namePtr) { + return std::string(); + } + + std::string name; + if (!ReadCString(*acc, namePtr, name)) { + return std::string(); + } + return name; + } + + // Get component by index + bool GetComponent(int index, std::uintptr_t& outNativeComponent) const { + outNativeComponent = 0; + if (!address || index < 0) return false; + + std::uintptr_t pool = 0; + if (!GetComponentPool(pool) || !pool) return false; + + std::int32_t count = 0; + if (!GetComponentCount(count) || index >= count) return false; + + std::uintptr_t slotAddr = pool + 0x8u + static_cast(index) * 0x10u; + return ReadPtrGlobal(slotAddr, outNativeComponent) && outNativeComponent != 0; + } +}; + +} // namespace UnityExternal diff --git a/External/GameObjectManager/Native/NativeTransform.hpp b/External/GameObjectManager/Native/NativeTransform.hpp new file mode 100644 index 0000000..af2bf9c --- /dev/null +++ b/External/GameObjectManager/Native/NativeTransform.hpp @@ -0,0 +1,233 @@ +#pragma once + +#include +#include + +#include "../../Core/UnityExternalMemory.hpp" +#include "../../Core/UnityExternalMemoryConfig.hpp" +#include "../../Core/UnityExternalTypes.hpp" +#include "NativeGameObject.hpp" + +namespace UnityExternal { + +struct Vector3f { + float x; + float y; + float z; +}; + +struct TransformHierarchyState { + std::uintptr_t nodeData; + std::uintptr_t parentIndices; +}; + +// Native Transform layout: +// +0x28 -> managed Transform pointer +// +0x30 -> native GameObject pointer +// +0x38 -> hierarchy state pointer +// +0x40 -> index in hierarchy (int32) + +struct NativeTransform { + std::uintptr_t address; + + NativeTransform() : address(0) {} + explicit NativeTransform(std::uintptr_t addr) : address(addr) {} + + bool IsValid() const { return address != 0; } + + // Get managed pointer (+0x28) + bool GetManaged(std::uintptr_t& outManaged) const { + outManaged = 0; + if (!address) return false; + return ReadPtrGlobal(address + 0x28u, outManaged) && outManaged != 0; + } + + // Get native GameObject (+0x30) + bool GetGameObject(std::uintptr_t& outGameObject) const { + outGameObject = 0; + if (!address) return false; + return ReadPtrGlobal(address + 0x30u, outGameObject) && outGameObject != 0; + } + + // Read hierarchy state for position calculation + bool ReadHierarchyState(TransformHierarchyState& outState, std::int32_t& outIndex) const { + if (!address) return false; + + std::uintptr_t statePtr = 0; + if (!ReadPtrGlobal(address + 0x38u, statePtr) || !statePtr) { + return false; + } + + if (!ReadPtrGlobal(statePtr + 0x18u, outState.nodeData)) { + return false; + } + + if (!ReadPtrGlobal(statePtr + 0x20u, outState.parentIndices)) { + return false; + } + + if (!ReadInt32Global(address + 0x40u, outIndex)) { + return false; + } + + if (!outState.nodeData || !outState.parentIndices || outIndex < 0) { + return false; + } + + return true; + } + + // Get world position + bool GetWorldPosition(Vector3f& outPos, int maxDepth = 256) const; +}; + +// Compute world position from hierarchy state (SSE optimized) +inline bool ComputeWorldPositionFromHierarchy(const TransformHierarchyState& state, + std::int32_t index, + Vector3f& outPos, + int maxDepth = 256) { + const IMemoryAccessor* mem = GetGlobalMemoryAccessor(); + if (!mem) return false; + + if (!state.nodeData || !state.parentIndices || index < 0 || maxDepth <= 0) { + return false; + } + + float selfNode[12] = {}; + std::uintptr_t selfAddr = state.nodeData + static_cast(index) * 48u; + if (!mem->Read(selfAddr, selfNode, sizeof(selfNode))) { + return false; + } + + __m128 acc = _mm_loadu_ps(selfNode); + + int parent = 0; + std::uintptr_t parentAddr = state.parentIndices + static_cast(index) * sizeof(std::int32_t); + if (!mem->Read(parentAddr, &parent, sizeof(parent))) { + return false; + } + + int depth = 0; + while (parent >= 0 && depth < maxDepth) { + float node[12] = {}; + std::uintptr_t nodeAddr = state.nodeData + static_cast(parent) * 48u; + if (!mem->Read(nodeAddr, node, sizeof(node))) { + return false; + } + + __m128 t = _mm_loadu_ps(node + 0); + __m128 qv = _mm_loadu_ps(node + 4); + __m128 m = _mm_loadu_ps(node + 8); + + __m128 v14 = _mm_mul_ps(m, acc); + + __m128i qvi = _mm_castps_si128(qv); + __m128 v15 = _mm_castsi128_ps(_mm_shuffle_epi32(qvi, 219)); + __m128 v16 = _mm_castsi128_ps(_mm_shuffle_epi32(qvi, 113)); + __m128 v17 = _mm_castsi128_ps(_mm_shuffle_epi32(qvi, 142)); + + __m128i v14i = _mm_castps_si128(v14); + __m128 v14_x = _mm_castsi128_ps(_mm_shuffle_epi32(v14i, 0)); + __m128 v14_y = _mm_castsi128_ps(_mm_shuffle_epi32(v14i, 85)); + __m128 v14_z = _mm_castsi128_ps(_mm_shuffle_epi32(v14i, 170)); + + const __m128 two = _mm_set1_ps(2.0f); + + __m128 q1 = _mm_castsi128_ps(_mm_shuffle_epi32(qvi, 85)); + __m128 q2 = _mm_castsi128_ps(_mm_shuffle_epi32(qvi, 170)); + __m128 q0 = _mm_castsi128_ps(_mm_shuffle_epi32(qvi, 0)); + + __m128 part0 = _mm_mul_ps( + _mm_sub_ps( + _mm_mul_ps(_mm_mul_ps(q1, two), v16), + _mm_mul_ps(_mm_mul_ps(q2, two), v17)), + v14_x); + + __m128 part1 = _mm_mul_ps( + _mm_sub_ps( + _mm_mul_ps(_mm_mul_ps(q2, two), v15), + _mm_mul_ps(_mm_mul_ps(q0, two), v16)), + v14_y); + + __m128 part2 = _mm_mul_ps( + _mm_sub_ps( + _mm_mul_ps(_mm_mul_ps(q0, two), v17), + _mm_mul_ps(_mm_mul_ps(q1, two), v15)), + v14_z); + + acc = _mm_add_ps(_mm_add_ps(_mm_add_ps(part0, v14), _mm_add_ps(part1, part2)), t); + + parentAddr = state.parentIndices + static_cast(parent) * sizeof(std::int32_t); + if (!mem->Read(parentAddr, &parent, sizeof(parent))) { + return false; + } + + ++depth; + } + + float tmp[4]; + _mm_storeu_ps(tmp, acc); + outPos.x = tmp[0]; + outPos.y = tmp[1]; + outPos.z = tmp[2]; + return true; +} + +inline bool NativeTransform::GetWorldPosition(Vector3f& outPos, int maxDepth) const { + TransformHierarchyState state{}; + std::int32_t index = 0; + if (!ReadHierarchyState(state, index)) { + return false; + } + return ComputeWorldPositionFromHierarchy(state, index, outPos, maxDepth); +} + +// Helper: Read hierarchy state from transform address +inline bool ReadTransformHierarchyState(std::uintptr_t transformAddress, + TransformHierarchyState& outState, + std::int32_t& outIndex) { + NativeTransform t(transformAddress); + return t.ReadHierarchyState(outState, outIndex); +} + +// Helper: Get world position from transform address +inline bool GetTransformWorldPosition(std::uintptr_t transformAddress, + Vector3f& outPos, + int maxDepth = 256) { + NativeTransform t(transformAddress); + return t.GetWorldPosition(outPos, maxDepth); +} + +// Find Transform component on a GameObject +inline bool FindTransformOnGameObject(RuntimeKind runtime, + std::uintptr_t gameObjectNative, + std::uintptr_t& outTransformNative) +{ + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc || !gameObjectNative) return false; + + NativeGameObject go(gameObjectNative); + + std::int32_t count = 0; + if (!go.GetComponentCount(count) || count <= 0) return false; + + for (std::int32_t i = 0; i < count; ++i) { + std::uintptr_t nativeComp = 0; + if (!go.GetComponent(i, nativeComp)) continue; + + std::uintptr_t managedComp = 0; + if (!ReadPtrGlobal(nativeComp + 0x28u, managedComp) || !managedComp) continue; + + TypeInfo info; + if (!GetManagedType(runtime, *acc, managedComp, info)) continue; + + if (info.name == "Transform") { + outTransformNative = nativeComp; + return true; + } + } + + return false; +} + +} // namespace UnityExternal diff --git a/External/GameObjectManager/UnityExternalGOM.hpp b/External/GameObjectManager/UnityExternalGOM.hpp new file mode 100644 index 0000000..cb49bf0 --- /dev/null +++ b/External/GameObjectManager/UnityExternalGOM.hpp @@ -0,0 +1,175 @@ +#pragma once + +#include +#include +#include + +#include "../Core/UnityExternalMemory.hpp" +#include "../Core/UnityExternalTypes.hpp" + +namespace UnityExternal { + +struct GameObjectEntry { + std::uintptr_t node; + std::uintptr_t nativeObject; + std::uintptr_t managedObject; +}; + +struct ComponentEntry { + std::uintptr_t nativeComponent; + std::uintptr_t managedComponent; +}; + +class GOMWalker { +public: + GOMWalker(const IMemoryAccessor& mem, RuntimeKind runtime) + : mem_(mem), runtime_(runtime) {} + + bool ReadManagerFromGlobal(std::uintptr_t gomGlobalAddress, std::uintptr_t& managerAddress) const; + bool EnumerateGameObjects(std::uintptr_t managerAddress, std::vector& out) const; + bool EnumerateGameObjectsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const; + bool EnumerateComponents(std::uintptr_t managerAddress, std::vector& out) const; + bool EnumerateComponentsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const; + + RuntimeKind GetRuntime() const { return runtime_; } + +private: + const IMemoryAccessor& mem_; + RuntimeKind runtime_; +}; + +inline bool GOMWalker::ReadManagerFromGlobal(std::uintptr_t gomGlobalAddress, std::uintptr_t& managerAddress) const { + managerAddress = 0; + if (!gomGlobalAddress) { + return false; + } + return ReadPtr(mem_, gomGlobalAddress, managerAddress); +} + +inline bool GOMWalker::EnumerateGameObjects(std::uintptr_t managerAddress, std::vector& out) const { + out.clear(); + if (!managerAddress) { + return false; + } + + std::uintptr_t listHead = 0; + if (!ReadPtr(mem_, managerAddress + 0x28, listHead) || !listHead) { + return false; + } + + const std::size_t kMaxObjects = 1000000; + std::uintptr_t firstNode = listHead; + std::uintptr_t tail = 0; + ReadPtr(mem_, firstNode + 0x0, tail); + + std::uintptr_t node = firstNode; + for (std::size_t i = 0; node && i < kMaxObjects; ++i) { + std::uintptr_t nativeObject = 0; + std::uintptr_t managedObject = 0; + std::uintptr_t next = 0; + + if (!ReadPtr(mem_, node + 0x10, nativeObject)) { + return false; + } + if (nativeObject) { + if (!ReadPtr(mem_, nativeObject + 0x28, managedObject)) { + return false; + } + } + + if (nativeObject || managedObject) { + GameObjectEntry entry{}; + entry.node = node; + entry.nativeObject = nativeObject; + entry.managedObject = managedObject; + out.push_back(entry); + } + + if (tail && node == tail) { + break; + } + + if (!ReadPtr(mem_, node + 0x8, next)) { + return false; + } + if (!next || next == firstNode) { + break; + } + + node = next; + } + + return true; +} + +inline bool GOMWalker::EnumerateGameObjectsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const { + std::uintptr_t managerAddress = 0; + if (!ReadManagerFromGlobal(gomGlobalAddress, managerAddress) || !managerAddress) { + return false; + } + return EnumerateGameObjects(managerAddress, out); +} + +inline bool GOMWalker::EnumerateComponents(std::uintptr_t managerAddress, std::vector& out) const { + out.clear(); + + std::vector gameObjects; + if (!EnumerateGameObjects(managerAddress, gameObjects)) { + return false; + } + if (gameObjects.empty()) { + return true; + } + + const int kMaxComponentsPerObject = 1024; + + for (const auto& info : gameObjects) { + if (!info.nativeObject) { + continue; + } + + std::uintptr_t componentPool = 0; + if (!ReadPtr(mem_, info.nativeObject + 0x30, componentPool) || !componentPool) { + continue; + } + + std::int32_t componentCount = 0; + if (!ReadInt32(mem_, info.nativeObject + 0x40, componentCount)) { + return false; + } + if (componentCount <= 0 || componentCount > kMaxComponentsPerObject) { + continue; + } + + for (int i = 0; i < componentCount; ++i) { + std::uintptr_t slotAddr = componentPool + 0x8 + static_cast(i) * 0x10; + + std::uintptr_t nativeComponent = 0; + if (!ReadPtr(mem_, slotAddr, nativeComponent) || !nativeComponent) { + continue; + } + + std::uintptr_t managedComponent = 0; + if (!ReadPtr(mem_, nativeComponent + 0x28, managedComponent) || !managedComponent) { + continue; + } + + ComponentEntry entry{}; + entry.nativeComponent = nativeComponent; + entry.managedComponent = managedComponent; + out.push_back(entry); + } + } + + return true; +} + +inline bool GOMWalker::EnumerateComponentsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const { + std::uintptr_t managerAddress = 0; + if (!ReadManagerFromGlobal(gomGlobalAddress, managerAddress) || !managerAddress) { + return false; + } + return EnumerateComponents(managerAddress, out); +} + +} // namespace UnityExternal diff --git a/External/MemoryRead/UnityExternalMemoryWinAPI.hpp b/External/MemoryRead/UnityExternalMemoryWinAPI.hpp new file mode 100644 index 0000000..bbff440 --- /dev/null +++ b/External/MemoryRead/UnityExternalMemoryWinAPI.hpp @@ -0,0 +1,62 @@ +#pragma once + +#if defined(_WIN32) || defined(_WIN64) +#define UNITY_EXTERNAL_WINDOWS 1 +#else +#define UNITY_EXTERNAL_WINDOWS 0 +#endif + +#if UNITY_EXTERNAL_WINDOWS + +#include +#include "../Core/UnityExternalMemory.hpp" + +namespace UnityExternal { + +// Default cross-process memory accessor based on WinAPI. +// Users can implement their own IMemoryAccessor (e.g. driver / custom API) +// and pass it to SetGlobalMemoryAccessor instead. +struct WinAPIMemoryAccessor : IMemoryAccessor { + HANDLE process; + + explicit WinAPIMemoryAccessor(HANDLE hProcess = GetCurrentProcess()) + : process(hProcess) {} + + bool Read(std::uintptr_t address, void* buffer, std::size_t size) const override { + if (!process || !buffer || size == 0) { + return false; + } + + SIZE_T bytesRead = 0; + if (!ReadProcessMemory(process, + reinterpret_cast(address), + buffer, + static_cast(size), + &bytesRead)) { + return false; + } + + return bytesRead == size; + } + + bool Write(std::uintptr_t address, const void* buffer, std::size_t size) const override { + if (!process || !buffer || size == 0) { + return false; + } + + SIZE_T bytesWritten = 0; + if (!WriteProcessMemory(process, + reinterpret_cast(address), + buffer, + static_cast(size), + &bytesWritten)) { + return false; + } + + return bytesWritten == size; + } +}; + +} // namespace UnityExternal + +#endif // UNITY_EXTERNAL_WINDOWS diff --git a/README.md b/README.md index 3cc65bc..d862240 100644 --- a/README.md +++ b/README.md @@ -1,285 +1,247 @@ -> [!IMPORTANT] -> 新版代码正在重构中 \ -> **The codebase is currently under refactoring.** +# UnityResolve.hpp -> 示例代码 (Example code) -> - [Phasmophobia Cheat (il2cpp, old)](https://github.com/issuimo/PhasmophobiaCheat/tree/main) -> - [SausageMan Cheat (il2cpp)](https://github.com/1992724048/SausageManCheat/tree/master/GPP32) +Unity 游戏逆向工具库,支持 **内注入** 和 **跨进程外挂** 两种模式。 -> [!NOTE]\ -> 有任何新功能建议或 Bug,欢迎直接提交 Issue;当然也欢迎你动手修改代码后向本仓库发起 Pull Request。 -> New feature requests or bug reports are welcome via Issues, and Pull Requests are just as appreciated if you’d like to contribute code directly. +> [!NOTE] +> 有任何新功能建议或 Bug,欢迎提交 Issue 或 Pull Request。 -> [!WARNING]\ -> 如果编译器支持,请务必开启 SEH(结构化异常处理)。 -> If your compiler supports it, please enable SEH (Structured Exception Handling). +--- -> [!TIP]\ -> 高版本 Android 上可能出现的崩溃问题,请参考 [此 Issue](https://github.com/issuimo/UnityResolve.hpp/issues/11)。 -> For potential crash issues on newer Android versions, see [this issue](https://github.com/issuimo/UnityResolve.hpp/issues/11). -
-

简要概述 (Brief overview)

-
+## 目录 -# UnityResolve.hpp -> ### 支持的平台 (Supported platforms) -> - [X] Windows -> - [X] Android -> - [X] Linux -> - [X] IOS -> - [X] HarmonyOS - -> ### 类型 (Types) -> - [X] Camera -> - [X] Transform -> - [X] Component -> - [X] Object (Unity) -> - [X] LayerMask -> - [X] Rigidbody -> - [x] MonoBehaviour -> - [x] Renderer -> - [x] Mesh -> - [X] Behaviour -> - [X] Physics -> - [X] GameObject -> - [X] Collider -> - [X] Vector4 -> - [X] Vector3 -> - [X] Vector2 -> - [X] Quaternion -> - [X] Bounds -> - [X] Plane -> - [X] Ray -> - [X] Rect -> - [X] Color -> - [X] Matrix4x4 -> - [X] Array -> - [x] String -> - [x] Object (C#) -> - [X] Type (C#) -> - [X] List -> - [X] Dictionary -> - [X] Animator -> - [X] CapsuleCollider -> - [X] BoxCollider -> - [X] Time -> - [X] FieldInfo -> - More... - -> ### 功能 (Functions) -> - [X] Mono注入 (Mono Inject) -> - [X] DumpToFile -> - [X] 附加线程 (Thread Attach / Detach) -> - [X] 修改静态变量值 (Modifying the value of a static variable) -> - [X] 获取对象 (Obtaining an instance) -> - [X] 创建C#字符串 (Create C# String) -> - [X] 创建C#数组 (Create C# Array) -> - [X] 创建C#对象 (Create C# instance) -> - [X] 世界坐标转屏幕坐标/屏幕坐标转世界坐标 (WorldToScreenPoint/ScreenToWorldPoint) -> - [X] 获取继承子类的名称 (Get the name of the inherited subclass) -> - [X] 获取函数地址(变量偏移) 及调用(修改/获取) (Get the function address (variable offset) and invoke (modify/get)) -> - [x] 获取Gameobject组件 (Get GameObject component) -> - More... -
-

功能使用 (How to use)

-
- -**GOM 支持 (GameObject Manager Support)** - -> **说明** -> -> 新增 `UnityResolve.GOM.hpp`(仅在 `WINDOWS_MODE` 下包含),用于通过 UnityPlayer 中的全局指针解析并枚举原生的 GameObject / Component 列表(基于原生内存读取)。此模块在某些场景下能补充托管 API 的不足,例如需要直接从原生层索引对象或在 IL2CPP 环境下结合托管对象做匹配。 - -- **平台**: 仅 Windows(`WINDOWS_MODE`)。 -- **要求**: 编译器需开启 SEH(结构化异常处理),否则读取内存时可能导致异常。 -- **主要 API 与类型**: - - `UnityResolveGOM::FindGameObjectManager()` - 查找 GOM 全局指针并返回地址信息 - - `UnityResolveGOM::EnumerateGameObjects()` - 枚举返回 `std::vector`,包含 `nativeObject` 与 `managedObject` - - `UnityResolveGOM::EnumerateComponents()` - 枚举返回 `std::vector`,包含 `nativeComponent` 与 `managedComponent` - - `UnityResolveGOM::EnumerateComponentsByGetComponents()` - 通过托管 API 获取 GameObject 并匹配 GOM 的原生指针 - -**示例用法** -```c++ +- [概述](#概述) +- [External 跨进程模块](#external-跨进程模块) +- [UnityResolve 内注入模块](#unityresolve-内注入模块) + +--- + +## 概述 + +| 模块 | 说明 | 平台 | +|------|------|------| +| **UnityResolve.hpp** | 内注入库,通过 DLL 注入调用托管 API | Windows / Android / Linux / iOS / HarmonyOS | +| **External/** | 跨进程外挂库,纯外部内存读取 | Windows | + +> [!WARNING] +> 如果编译器支持,请务必开启 SEH(结构化异常处理)。 + +> [!TIP] +> 高版本 Android 崩溃问题请参考 [Issue #11](https://github.com/issuimo/UnityResolve.hpp/issues/11)。 + +**示例项目** +- [Phasmophobia Cheat (IL2CPP)](https://github.com/issuimo/PhasmophobiaCheat) +- [SausageMan Cheat (IL2CPP)](https://github.com/1992724048/SausageManCheat/tree/master/GPP32) + +--- + +## External 跨进程模块 + +独立于内注入的**纯外部内存读取模块**,适用于跨进程外挂开发。 + +### 目录结构 + +``` +External/ +├── Core/ # 基础内存接口 +│ ├── UnityExternalMemory.hpp # IMemoryAccessor 接口 +│ ├── UnityExternalMemoryConfig.hpp # 全局访问器 + ReadPtrGlobal 等 +│ └── UnityExternalTypes.hpp # RuntimeKind / TypeInfo / GetManagedType +│ +├── MemoryRead/ # 内存读取实现(可替换) +│ └── UnityExternalMemoryWinAPI.hpp # 默认 WinAPI 实现 +│ +├── GameObjectManager/ # GOM 遍历 + 原生结构 +│ ├── UnityExternalGOM.hpp # GOMWalker +│ ├── Managed/ManagedObject.hpp # 托管对象封装 +│ └── Native/ # 原生结构 +│ ├── NativeGameObject.hpp +│ ├── NativeComponent.hpp +│ └── NativeTransform.hpp +│ +├── Camera/ # 相机 + W2S +│ ├── UnityExternalCamera.hpp # FindMainCamera / Camera_GetMatrix +│ └── UnityExternalWorldToScreen.hpp # WorldToScreenPoint +│ +└── ExternalResolve.hpp # 统一入口 +``` + +### 快速开始 + +```cpp +#include "External/ExternalResolve.hpp" + +// 1. 创建内存访问器 +HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid); +UnityExternal::WinAPIMemoryAccessor accessor(hProcess); +UnityExternal::SetGlobalMemoryAccessor(&accessor); + +// 2. 遍历所有 GameObject +std::uintptr_t gomGlobal = unityPlayerBase + GOM_OFFSET; +UnityExternal::GOMWalker walker(accessor, UnityExternal::RuntimeKind::Mono); + +std::vector gameObjects; +walker.EnumerateGameObjectsFromGlobal(gomGlobal, gameObjects); + +for (const auto& go : gameObjects) { + UnityExternal::NativeGameObject nativeGO(go.nativeObject); + std::string name = nativeGO.GetName(); + // ... +} + +// 3. 获取 Transform 世界坐标 +UnityExternal::Vector3f pos; +UnityExternal::GetTransformWorldPosition(transformNative, pos); + +// 4. 相机 W2S +glm::mat4 camMatrix; +UnityExternal::Camera_GetMatrix(cameraNative, camMatrix); +auto result = UnityExternal::WorldToScreenPoint(camMatrix, screen, glm::vec3(pos.x, pos.y, pos.z)); +``` + +### 自定义内存访问器 + +默认使用 `WinAPIMemoryAccessor`(基于 `ReadProcessMemory`),可替换为驱动或其他实现: + +```cpp +class MyDriverAccessor : public UnityExternal::IMemoryAccessor { +public: + bool Read(std::uintptr_t address, void* buffer, std::size_t size) const override { + return MyDriver::ReadMemory(address, buffer, size); + } + bool Write(std::uintptr_t address, const void* buffer, std::size_t size) const override { + return MyDriver::WriteMemory(address, buffer, size); + } +}; + +MyDriverAccessor accessor; +UnityExternal::SetGlobalMemoryAccessor(&accessor); +``` + +--- + +## UnityResolve 内注入模块 + +通过 DLL 注入使用托管 API。 + +### 支持平台 + +- [x] Windows +- [x] Android +- [x] Linux +- [x] iOS +- [x] HarmonyOS + +### 支持类型 + +Camera, Transform, Component, GameObject, Rigidbody, MonoBehaviour, Renderer, Mesh, Physics, Collider, Vector2/3/4, Quaternion, Matrix4x4, Array, List, Dictionary, String, Animator, Time, FieldInfo 等。 + +### 依赖 + +> [!CAUTION] +> 新版本强制要求 [GLM 库](https://github.com/g-truc/glm) + +### 基础用法 + +#### 更改平台 +```cpp +#define WINDOWS_MODE 1 +#define ANDROID_MODE 0 +#define LINUX_MODE 0 +``` + +#### 初始化 +```cpp +// Windows +UnityResolve::Init(GetModuleHandle(L"GameAssembly.dll"), UnityResolve::Mode::Il2Cpp); +// Linux / Android / iOS +UnityResolve::Init(dlopen("libil2cpp.so", RTLD_NOW), UnityResolve::Mode::Il2Cpp); +``` + +#### 线程附加 +```cpp +UnityResolve::ThreadAttach(); +// ... 操作 ... +UnityResolve::ThreadDetach(); +``` + +#### 获取类和方法 +```cpp +auto assembly = UnityResolve::Get("Assembly-CSharp.dll"); +auto pClass = assembly->Get("PlayerController"); + +// 获取字段 +auto field = pClass->Get("health"); +int health = pClass->GetValue(playerInstance, "health"); +pClass->SetValue(playerInstance, "health", 100); + +// 调用方法 +auto method = pClass->Get("TakeDamage"); +method->Invoke(playerInstance, 50); +``` + +#### W2S +```cpp +Camera* pCamera = UnityResolve::UnityType::Camera::GetMain(); +Vector3 screenPos = pCamera->WorldToScreenPoint(worldPos, Eye::Left); +``` + +#### Dump +```cpp +UnityResolve::DumpToFile("./output/"); +``` + +### GOM 内注入支持 + +`UnityResolve.GOM.hpp` 提供内注入环境下的原生 GOM 遍历(仅 Windows): + +```cpp #include "UnityResolve.hpp" #if WINDOWS_MODE #include "UnityResolve.GOM.hpp" #endif -// 初始化 UnityResolve(示例) -UnityResolve::Init(GetModuleHandle(L"GameAssembly.dll | mono.dll"), UnityResolve::Mode::Mono); -UnityResolve::ThreadAttach(); - auto gos = UnityResolveGOM::EnumerateGameObjects(); -for (const auto &g : gos) { - // g.nativeObject 是原生指针,g.managedObject 是托管的 GameObject* +for (const auto& g : gos) { + // g.nativeObject / g.managedObject } +``` -auto comps = UnityResolveGOM::EnumerateComponents(); -for (const auto &c : comps) { - // c.nativeComponent / c.managedComponent -} +> [!WARNING] +> 需开启 SEH,不同 Unity 版本可能存在兼容性差异。 + +### 更多用法 + +#### Mono 注入 +```cpp +// 仅 Mono 模式 +UnityResolve::AssemblyLoad assembly("./MonoCsharp.dll"); ``` -**注意**: 该模块通过在 UnityPlayer 模块中查找全局指针并直接读取内存实现,对不同 Unity 版本或自定义引擎导出可能存在兼容性差异,如发生异常或数据不一致,请提交 Issue 并提供 Unity 版本与相关模块信息。 +#### 创建 C# 对象 +```cpp +// 字符串 +auto str = UnityResolve::UnityType::String::New("hello"); +// 数组 +auto array = UnityResolve::UnityType::Array::New(pClass, 10); -#### 使用GLM (use glm) -> [!CAUTION] -> 新版本强制性要求 \ -> Mandatory requirements for new versions - -[GLM Library](https://github.com/g-truc/glm) -> ``` C++ -> #define USE_GLM // 新版本不需要添加 (New versions do not need to be added) -> #include "UnityResolve.hpp" -> ``` - -#### 更改平台 (Change platform) -> ``` c++ -> #define WINDOWS_MODE 1 // 如果需要请改为 1 (1 if you need) -> #define ANDROID_MODE 0 -> #define LINUX_MODE 0 -> ``` - -#### 初始化 (Initialization) -> ``` c++ -> // Windows -> UnityResolve::Init(GetModuleHandle(L"GameAssembly.dll | mono.dll"), UnityResolve::Mode::Mono); -> // Linux、Android、IOS、HarmonyOS -> UnityResolve::Init(dlopen(L"GameAssembly.so | mono.so", RTLD_NOW), UnityResolve::Mode::Mono); -> ``` - -#### 附加线程 (Thread Attach / Detach) -> [!TIP] -> 如果你是在游戏主线程使用或者通过Hook Update/LateUpdate 那么并不需要该功能 \ -> If you are using it on the main thread of the game or via Hook Update/LateUpdate, you don't need this feature - -> ``` c++ -> // C# GC Attach -> UnityResolve::ThreadAttach(); -> -> // C# GC Detach -> UnityResolve::ThreadDetach(); -> ``` - -#### Mono注入 (Mono Inject) -> [!TIP] -> 仅 Mono 模式可用 \ -> Only Mono mode is available - -> ``` c++ -> UnityResolve::AssemblyLoad assembly("./MonoCsharp.dll"); -> UnityResolve::AssemblyLoad assembly("./MonoCsharp.dll", "MonoCsharp", "Inject", "MonoCsharp.Inject:Load()"); -> ``` - -#### 获取函数地址(变量偏移) 及调用(修改/获取) (Get the function address (variable offset) and invoke (modify/get)) -> ``` c++ -> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll"); -> const auto pClass = assembly->Get("className | 类名称"); -> // assembly->Get("className | 类名称", "*"); -> // assembly->Get("className | 类名称", "namespace | 空间命名"); -> -> const auto field = pClass->Get("Field Name | 变量名"); -> const auto fieldOffset = pClass->Get("Field Name | 变量名"); -> const int time = pClass->GetValue(obj Instance | 对象地址, "time"); -> // pClass->GetValue(obj Instance*, name); -> = pClass->SetValue(obj Instance | 对象地址, "time", 114514); -> // pClass->SetValue(obj Instance*, name, value); -> const auto method = pClass->Get("Method Name | 函数名"); -> // pClass->Get("Method Name | 函数名", { "System.String" }); -> // pClass->Get("Method Name | 函数名", { "*", "System.String" }); -> // pClass->Get("Method Name | 函数名", { "*", "", "System.String" }); -> // pClass->Get("Method Name | 函数名", { "*", "System.Int32", "System.String" }); -> // pClass->Get("Method Name | 函数名", { "*", "System.Int32", "System.String", "*" }); -> // "*" == "" -> -> const auto functionPtr = method->function; -> -> const auto method1 = pClass->Get("method name1 | 函数名称1"); -> const auto method2 = pClass->Get("method name2 | 函数名称2"); -> -> method1->Invoke(114, 514, "114514"); -> // Invoke(args...); -> -> // Cast(void); -> // Cast(UnityResolve::MethodPointer&); -> const UnityResolve::MethodPointer ptr = method2->Cast(); -> ptr(114514, true); -> -> UnityResolve::MethodPointer add; -> ptr = method1->Cast(add); -> -> std::function add2; -> method->Cast(add2); -> -> UnityResolve::Field::Variable syncPos; -> syncPos.Init(pClass->Get("syncPos")); -> auto pos = syncPos[playerInstance]; -> auto pos = syncPos.Get(playerInstance); -> -> ``` - -#### 转存储到文件 (DumpToFile) -> ``` C++ -> UnityResolve::DumpToFile("./output/"); -> ``` - -#### 创建C#字符串 (Create C# String) -> ``` c++ -> const auto str = UnityResolve::UnityType::String::New("string | 字符串"); -> std::string cppStr = str.ToString(); -> ``` - -#### 创建C#数组 (Create C# Array) -> ``` c++ -> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll"); -> const auto pClass = assembly->Get("className | 类名称"); -> const auto array = UnityResolve::UnityType::Array::New(pClass, size); -> std::vector cppVector = array.ToVector(); -> ``` - -#### 创建C#对象 (Create C# instance) -> ``` c++ -> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll"); -> const auto pClass = assembly->Get("className | 类名称"); -> const auto pGame = pClass->New(); -> ``` - -#### 获取对象 (Obtaining an instance) -> [!TIP] -> 仅 Unity 对象 \ -> Unity objects only - -> ``` c++ -> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll"); -> const auto pClass = assembly->Get("className | 类名称"); -> std::vector playerVector = pClass->FindObjectsByType(); -> // FindObjectsByType(void); -> playerVector.size(); -> ``` - -#### 世界坐标转屏幕坐标/屏幕坐标转世界坐标 (WorldToScreenPoint/ScreenToWorldPoint) -> ``` c++ -> Camera* pCamera = UnityResolve::UnityType::Camera::GetMain(); -> Vector3 point = pCamera->WorldToScreenPoint(Vector3, Eye::Left); -> Vector3 world = pCamera->ScreenToWorldPoint(point, Eye::Left); -> ``` - -#### 获取继承子类的名称 (Get the name of the inherited subclass) -> ``` c++ -> const auto assembly = UnityResolve::Get("UnityEngine.CoreModule.dll"); -> const auto pClass = assembly->Get("MonoBehaviour"); -> Parent* pParent = pClass->FindObjectsByType()[0]; -> std::string child = pParent->GetType()->GetFullName(); -> ``` - -#### 获取Gameobject组件 (Get GameObject component) -> ``` c++ -> std::vector objs = gameobj->GetComponents(UnityResolve::Get("assembly.dll")->Get("class"))); -> // gameobj->GetComponents(Class* component) -> std::vector objs = gameobj->GetComponentsInChildren(UnityResolve::Get("assembly.dll")->Get("class"))); -> // gameobj->GetComponentsInChildren(Class* component) -> std::vector objs = gameobj->GetComponentsInParent(UnityResolve::Get("assembly.dll")->Get("class"))); -> // gameobj->GetComponentsInParent(Class* component) -> ``` +// 实例 +auto obj = pClass->New(); +``` +#### 查找对象 +```cpp +// 查找所有指定类型的对象 +std::vector players = pClass->FindObjectsByType(); + +// 获取组件 +auto comps = gameobj->GetComponents(pClass); +auto children = gameobj->GetComponentsInChildren(pClass); +``` + +#### 获取子类类型名 +```cpp +auto pClass = UnityResolve::Get("UnityEngine.CoreModule.dll")->Get("MonoBehaviour"); +auto obj = pClass->FindObjectsByType()[0]; +std::string typeName = obj->GetType()->GetFullName(); +``` diff --git a/UnityResolve.GOM.hpp b/UnityResolve.GOM.hpp index fd2323f..5e2d48d 100644 --- a/UnityResolve.GOM.hpp +++ b/UnityResolve.GOM.hpp @@ -377,7 +377,7 @@ inline std::vector EnumerateComponents() { continue; } - auto componentCount = SafeReadInt32(nativeGo + 0x38); + auto componentCount = SafeReadInt32(nativeGo + 0x40); if (componentCount <= 0 || componentCount > kMaxComponentsPerObject) { continue; } diff --git a/UnityResolve.hpp b/UnityResolve.hpp index dc4ae6b..ce3741f 100644 --- a/UnityResolve.hpp +++ b/UnityResolve.hpp @@ -1563,8 +1563,10 @@ class UnityResolve final { char pad_0000[0x28]; void *managedObject; void *componentPool; + char pad_0038[0x8]; int componentCount; - char pad_003C[0x4]; + char pad_0044[0x1C]; + void *objectName; }; struct Il2CppComponentPool { @@ -1605,8 +1607,10 @@ class UnityResolve final { char pad_0000[0x28]; void *managedObject; void *componentPool; + char pad_0038[0x8]; int componentCount; - char pad_003C[0x4]; + char pad_0044[0x1C]; + void *objectName; }; struct MonoComponentPool { From eae0f45034bbf6f01536ce66ede5ccea2246cf2e Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Thu, 11 Dec 2025 01:18:26 +0800 Subject: [PATCH 6/9] Delete UnityResolve_Usage.md --- UnityResolve_Usage.md | 355 ------------------------------------------ 1 file changed, 355 deletions(-) delete mode 100644 UnityResolve_Usage.md diff --git a/UnityResolve_Usage.md b/UnityResolve_Usage.md deleted file mode 100644 index c931fc8..0000000 --- a/UnityResolve_Usage.md +++ /dev/null @@ -1,355 +0,0 @@ -# UnityResolve 支持库用法总结 - -> 本文说明 `UnityResolve.hpp` 与 `UnityResolve.GOM.hpp` 的核心用法,便于在任意 Unity/Mono/Il2Cpp 游戏中快速集成。 - ---- - -## 1. UnityResolve.hpp 基本概念 - -### 1.1 初始化与运行模式 - -- **运行模式**: - - `UnityResolve::Mode::Il2Cpp` - - `UnityResolve::Mode::Mono` -- **初始化入口**: - - `UnityResolve::Init(void* hmodule, UnityResolve::Mode mode)` - - `hmodule`: - - Il2Cpp:`HMODULE gameAsm = GetModuleHandleW(L"GameAssembly.dll");` - - Mono:`HMODULE mono = GetModuleHandleW(L"mono-2.0-bdwgc.dll");` 等 - -示例: - -```cpp -HMODULE hGameAsm = GetModuleHandleW(L"GameAssembly.dll"); -if (hGameAsm) { - UnityResolve::Init(hGameAsm, UnityResolve::Mode::Il2Cpp); -} else { - HMODULE hMono = GetModuleHandleW(L"mono.dll"); - if (hMono) UnityResolve::Init(hMono, UnityResolve::Mode::Mono); -} -``` - -### 1.2 线程附加 / 分离 - -- 在非 Unity 主线程中调用托管 API 时,需要先附加线程: - - `UnityResolve::ThreadAttach();` -- 结束时可选调用: - - `UnityResolve::ThreadDetach();` - -通常做法: - -```cpp -DWORD WINAPI WorkerThread(LPVOID) { - UnityResolve::ThreadAttach(); - // 托管调用逻辑 - UnityResolve::ThreadDetach(); - return 0; -} -``` - -### 1.3 程序集与类查询 - -- 全局程序集列表:`UnityResolve::assembly`(`std::vector`) -- 按名称获取程序集: - -```cpp -UnityResolve::Assembly* core = UnityResolve::Get("UnityEngine.CoreModule.dll"); -UnityResolve::Assembly* game = UnityResolve::Get("BlueArchive.dll"); -``` - -- 在程序集内查找类: - -```cpp -using UR = UnityResolve; -UR::Assembly* asmBA = UR::Get("BlueArchive.dll"); -if (asmBA) { - // 按类名 + 命名空间 + 父类名(后两个可用 "*" 通配) - UR::Class* battle = asmBA->Get("Battle", "MX.Logic.Battles"); -} -``` - -### 1.4 字段与方法查询 - -在 `UnityResolve::Class` 上提供统一的 `Get` 接口: - -- 获取字段对象:`Field*`: - -```cpp -UnityResolve::Field* fld = battle->Get("playerGroup"); -int32_t offset = fld ? fld->offset : 0; -``` - -- 直接按字段名获取偏移:`int32_t*`: - -```cpp -int32_t* pOffset = battle->Get("playerGroup"); -int32_t offset = pOffset ? *pOffset : 0; -``` - -- 获取方法:`Method*`,可带参数类型过滤: - -```cpp -// 按方法名查找(不区分重载) -UnityResolve::Method* m1 = battle->Get("Tick"); - -// 带参数类型过滤(参数列表长度与类型名匹配) -UnityResolve::Method* m2 = battle->Get( - "DoSomething", - {"System.Int32", "System.String"} -); -``` - -### 1.5 字段读写辅助 - -`Class` 提供了简化的实例字段读写封装: - -```cpp -// 按字段名读写 -int v = battle->GetValue(obj, "hp"); -battle->SetValue(obj, "hp", 9999); - -// 按偏移读写 -int v2 = battle->GetValue(obj, offset); -battle->SetValue(obj, offset, 123); -``` - -### 1.6 方法调用(Invoke / RuntimeInvoke) - -#### 1.6.1 直接函数指针调用:`Method::Invoke` - -适用于:方法已经编译成 native(Il2Cpp 默认如此,Mono 需 `Compile()`)。 - -```cpp -UnityResolve::Method* method = battle->Get("get_PlayerCount"); -if (method) { - int count = method->Invoke(instance /*this*/); -} -``` - -#### 1.6.2 runtime 调用:`Method::RuntimeInvoke` - -封装了 `il2cpp_runtime_invoke` / `mono_runtime_invoke`: - -```cpp -UnityResolve::Method* method = battle->Get("Tick"); -if (method) { - method->RuntimeInvoke(instance /*this*/); -} -``` - -### 1.7 UnityType 辅助结构 - -`UnityResolve::UnityType` 内提供常用 Unity 结构体 / 类的 C++ 映射和静态方法,例如: - -- 向量与矩阵:`Vector2`/`Vector3`/`Vector4`/`Quaternion`/`Matrix4x4` -- UnityEngine 对象包装:`GameObject`/`Transform`/`Camera`/`Rigidbody` 等 -- 集合类型:`UnityType::Array` / `List` / `Dictionary` - -示例:获取所有摄像机: - -```cpp -using UR = UnityResolve; -std::vector cams = UR::UnityType::Camera::GetAllCamera(); -``` - -示例:通过 `GameObject` 获取组件: - -```cpp -using UR = UnityResolve; -UR::UnityType::GameObject* go = UR::UnityType::GameObject::Find("Player"); -if (go) { - UR::UnityType::Transform* tr = go->GetTransform(); -} -``` - ---- - -## 2. UnityResolve.GOM.hpp:GOM 探测 - -`UnityResolve.GOM.hpp` 提供自动定位 Unity `GameObjectManager` 全局指针的封装 API,无需手动维护偏移表。 - -### 2.1 平台要求 - -- **仅 Windows**:依赖 `WINDOWS_MODE` 和 `Psapi.h` -- **运行时支持**:Mono 和 IL2CPP -- **前置条件**: - - `UnityPlayer.dll` 已加载 - - `UnityEngine.CoreModule.dll` 和 `Camera.get_main` 可访问 - -### 2.2 核心 API - -```cpp -struct UnityGOMInfo { - std::uintptr_t address; // GOM 全局变量的绝对地址 - std::uintptr_t offset; // 相对 UnityPlayer.dll 的偏移 -}; - -UnityGOMInfo UnityResolveGOM::FindGameObjectManager(); -``` - -**返回值**: -- `address == 0`:扫描失败 -- `address != 0`:成功,`offset` 可用于跨版本适配 - -### 2.3 内部原理概览(可选阅读) - -内部实现大致会完成以下几步(仅作概念说明,具体指令匹配与偏移细节以源码为准): - -1. **检测运行时类型** - - Il2Cpp:优先检测 `GameAssembly.dll` - - Mono:在一组常见 mono 模块名中查找已加载模块 - -2. **获取扫描起点** - - 使用 `UnityResolve::Init` + `UnityResolve::ThreadAttach` 完成托管运行时初始化 - - 通过反射拿到 `UnityEngine.Camera.get_main` 的 native 函数指针,作为入口起点 - -3. **沿调用链解析 GOM 全局指针** - - 在入口附近跟踪进入 `UnityPlayer.dll` 的调用 - - 在 UnityPlayer 的相关函数中,解析指向全局区域的访问,并识别出 `GameObjectManager` 全局变量 - -运行时会输出简单日志,示例: - -```text -Runtime: IL2CPP -GameObjectManager->UnityPlayer.dll+0x1D15C78 -``` - -### 2.4 使用示例 - -```cpp -#include -#include -#include "UnityResolve.hpp" - -DWORD WINAPI MainThread(LPVOID param) { - HMODULE hModule = static_cast(param); - AllocConsole(); - FILE* fp; - freopen_s(&fp, "CONOUT$", "w", stdout); - - std::cout << "[UnityCatcher] Installation Successful" << std::endl; - - UnityGOMInfo gom = UnityResolveGOM::FindGameObjectManager(); - if (!gom.address) { - std::cout << "GameObjectManager not found" << std::endl; - } - - std::cout << "Exiting..." << std::endl; - FreeConsole(); - FreeLibraryAndExitThread(hModule, 0); - return 0; -} - -BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID) { - if (ul_reason_for_call == DLL_PROCESS_ATTACH) { - DisableThreadLibraryCalls(hModule); - HANDLE hThread = CreateThread(nullptr, 0, MainThread, hModule, 0, nullptr); - if (hThread) CloseHandle(hThread); - } - return TRUE; -} -``` - -**注意**:`FindGameObjectManager()` 在 Debug/开发阶段会打印少量扫描日志(运行时类型、GOM 偏移等),Release 版可按需关闭这些输出。 - ---- - -## 3. 基于 GOM 的遍历接口 - -### 3.1 GameObject 遍历 - -`UnityResolve.GOM.hpp` 内提供对 GOM 链表的统一封装: - -```cpp -struct GameObjectInfo { - std::uintptr_t nativeObject; // 原生 GameObject 指针 - UnityResolve::UnityType::GameObject* managedObject; // 托管 GameObject 实例 -}; - -std::vector UnityResolveGOM::EnumerateGameObjects(); -``` - -- 返回值包含 GOM 管理的所有 GameObject(过滤掉空指针)。 -- 需要在 `FindGameObjectManager()` 成功之后使用。 - -### 3.2 Component 遍历(原生组件池) - -```cpp -struct ComponentInfo { - std::uintptr_t nativeComponent; // 原生 Component 指针 - UnityResolve::UnityType::Component* managedComponent; // 托管 Component 实例 -}; - -std::vector UnityResolveGOM::EnumerateComponents(); -``` - -- 直接通过原生 `GameObject` 的组件池遍历组件。 -- 适合需要同时关注 native/managed 指针结构的场景。 - -### 3.3 Component 遍历(推荐:托管 GetComponents) - -```cpp -std::vector UnityResolveGOM::EnumerateComponentsByGetComponents(); -``` - -- 使用托管 `GameObject.GetComponents` 统一获取组件列表,然后为每个组件补充原生指针。 -- 只保留 GOM 中存在的 GameObject 对应的组件,更接近 Unity“逻辑视角”的组件集合。 -- 推荐用于**统计 / 打印所有组件类型**等用途。 - -### 3.4 统计与打印示例 - -下例展示如何统计并打印所有组件(示例逻辑与 `example_UnityCatcher.cpp` 保持一致,只保留核心部分): - -```cpp -using UR = UnityResolve; - -// 1. 自动定位 GOM -UnityGOMInfo gom = UnityResolveGOM::FindGameObjectManager(); -if (!gom.address) { - std::cout << "GameObjectManager not found" << std::endl; - return; -} - -// 2. 遍历 GameObject 和 Component -auto gameObjects = UnityResolveGOM::EnumerateGameObjects(); -auto components = UnityResolveGOM::EnumerateComponentsByGetComponents(); - -std::cout << "GameObjects: " << gameObjects.size() << std::endl; -std::cout << "Components: " << components.size() << std::endl; - -// 3. 打印每个组件的原生地址与完整类型名 -for (const auto &c : components) { - if (!c.managedComponent) continue; - - auto type = c.managedComponent->GetType(); - if (!type) continue; - - const char* ns = type->GetNamespace(); - const char* name = type->GetName(); - - std::cout << std::hex << std::uppercase - << "0x" << c.nativeComponent << "-" - << (ns && *ns ? ns : "None") << "." << (name ? name : "") - << std::dec << std::nouppercase - << std::endl; -} -``` - ---- - -## 4. 集成建议 - -1. **一次性初始化**: - - `UnityResolve::Init` + `ThreadAttach` 只需在进程启动时调用一次 - - `FindGameObjectManager()` 在启动阶段调用一次,缓存 `offset` - -2. **性能优化**: - - 避免在高频逻辑中重复调用 GOM 扫描 - - 将 `offset` 保存为全局常量,用于后续 GOM 遍历 - -3. **架构分层**: - - **UnityResolve**:处理反射(类/字段/方法)和托管调用 - - **GOM 扫描**:一次性定位 GameObjectManager 地址 - - **业务逻辑**:使用 GOM 地址遍历场景对象 - -这样实现"通用框架 + 游戏业务"解耦,提升跨版本适配能力. From 8bc768112a91913c1df278e329a346d86c9751e9 Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Thu, 11 Dec 2025 01:25:22 +0800 Subject: [PATCH 7/9] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重交 --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index d862240..ef833a9 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,26 @@ Unity 游戏逆向工具库,支持 **内注入** 和 **跨进程外挂** 两 独立于内注入的**纯外部内存读取模块**,适用于跨进程外挂开发。 +### 依赖 + +需要手动添加 [GLM 库](https://github.com/g-truc/glm): + +```bash +# 克隆 GLM 到项目根目录 +git clone https://github.com/g-truc/glm.git +# 或下载 Release 解压到项目根目录 +``` + +确保目录结构为: +``` +项目根目录/ +├── glm/ +│ ├── glm.hpp +│ └── ... +└── External/ + └── ... +``` + ### 目录结构 ``` From 659498dae8f35ea984e3fa43b99e68e2ba05bbb9 Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Fri, 12 Dec 2025 00:27:16 +0800 Subject: [PATCH 8/9] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 算法还原 --- External/Camera/UnityExternalCamera.hpp | 141 +++--- .../Managed/ManagedObject.hpp | 14 - .../Native/NativeGameObject.hpp | 29 ++ .../GameObjectManager/UnityExternalGOM.hpp | 439 ++++++++++++++++-- External/README.md | 175 +++++++ 5 files changed, 658 insertions(+), 140 deletions(-) create mode 100644 External/README.md diff --git a/External/Camera/UnityExternalCamera.hpp b/External/Camera/UnityExternalCamera.hpp index e85ef28..5470b8d 100644 --- a/External/Camera/UnityExternalCamera.hpp +++ b/External/Camera/UnityExternalCamera.hpp @@ -3,44 +3,49 @@ #include #include #include +#include #include "../Core/UnityExternalMemory.hpp" #include "../Core/UnityExternalMemoryConfig.hpp" #include "../Core/UnityExternalTypes.hpp" #include "../GameObjectManager/UnityExternalGOM.hpp" +#include "../GameObjectManager/Native/NativeGameObject.hpp" #include "glm/glm.hpp" #include "glm/gtc/type_ptr.hpp" namespace UnityExternal { -// Read camera's view-projection matrix +// Read camera's matrix directly from +0x100 (as provided by Unity) // nativeCamera + 0x100 -> 4x4 matrix (16 floats) inline bool Camera_GetMatrix(std::uintptr_t nativeCamera, glm::mat4& outMatrix) { outMatrix = glm::mat4(1.0f); - if (!nativeCamera) { - return false; - } + if (!nativeCamera) return false; const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); - if (!acc) { - return false; - } + if (!acc) return false; float data[16] = {}; - if (!acc->Read(nativeCamera + 0x100u, data, sizeof(data))) { - return false; - } + if (!acc->Read(nativeCamera + 0x100u, data, sizeof(data))) return false; outMatrix = glm::make_mat4(data); return true; } -// Find main camera with priority: -// 1. GameObject name "Main Camera" -// 2. GameObject name "Camera Top" -// 3. First enabled Camera component + +// Check if component is enabled (nativeComponent + 0x38 -> enabled byte) +inline bool IsComponentEnabled(std::uintptr_t nativeComponent) +{ + if (!nativeComponent) return false; + std::uint8_t enabled = 0; + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc) return false; + if (!acc->Read(nativeComponent + 0x38u, &enabled, 1)) return true; // Assume enabled if can't read + return enabled != 0; +} + +// Find main camera: use GOMWalker helpers to get tag=5 GameObject, then find Camera component on it. inline bool FindMainCamera(const GOMWalker& walker, std::uintptr_t gomGlobalAddress, std::uintptr_t& outNativeCamera, @@ -50,92 +55,50 @@ inline bool FindMainCamera(const GOMWalker& walker, outManagedCamera = 0; const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); - if (!acc || !gomGlobalAddress) { + if (!acc || !gomGlobalAddress) return false; + + std::uintptr_t mainGoNative = 0; + std::uintptr_t mainGoManaged = 0; + if (!FindGameObjectThroughTag(walker, gomGlobalAddress, 5, mainGoNative, mainGoManaged) || !mainGoNative) { return false; } - std::vector components; - if (!walker.EnumerateComponentsFromGlobal(gomGlobalAddress, components)) { + std::uintptr_t componentArray = 0; + if (!ReadPtrGlobal(mainGoNative + 0x30u, componentArray) || !componentArray) { return false; } - if (components.empty()) { + + std::int32_t componentCount = 0; + if (!ReadInt32Global(mainGoNative + 0x40u, componentCount) || componentCount <= 0 || componentCount > 1024) { return false; } - RuntimeKind runtime = walker.GetRuntime(); - - // Candidates - std::uintptr_t mainCameraNative = 0, mainCameraManaged = 0; - std::uintptr_t cameraTopNative = 0, cameraTopManaged = 0; - std::uintptr_t firstEnabledNative = 0, firstEnabledManaged = 0; - - for (const auto& entry : components) { - if (!entry.managedComponent || !entry.nativeComponent) { - continue; - } - - TypeInfo typeInfo; - if (!GetManagedType(runtime, *acc, entry.managedComponent, typeInfo)) { - continue; + for (int c = 0; c < componentCount; ++c) + { + std::uintptr_t compEntryAddr = componentArray + static_cast(c) * 16u; + std::uintptr_t nativeComp = 0; + if (!ReadPtrGlobal(compEntryAddr + 0x8u, nativeComp) || !nativeComp) continue; + + std::uintptr_t managedComp = 0; + ReadPtrGlobal(nativeComp + 0x28u, managedComp); + if (!managedComp) continue; + + std::string typeName; + std::uintptr_t vtable = 0, monoClass = 0, namePtr = 0; + if (ReadPtrGlobal(managedComp + 0x0u, vtable) && + ReadPtrGlobal(vtable + 0x0u, monoClass) && + ReadPtrGlobal(monoClass + 0x48u, namePtr)) { + ReadCString(*acc, namePtr, typeName); } - if (typeInfo.name != "Camera") { - continue; + if (typeName == "Camera") { + if (!IsComponentEnabled(nativeComp)) { + continue; + } + outNativeCamera = nativeComp; + outManagedCamera = managedComp; + return true; } - - // nativeCamera+0x30 -> GameObject native - std::uintptr_t goNative = 0; - if (!ReadPtrGlobal(entry.nativeComponent + 0x30u, goNative) || !goNative) { - continue; - } - - // GameObject+0x60 -> name pointer - std::uintptr_t namePtr = 0; - if (!ReadPtr(*acc, goNative + 0x60u, namePtr) || !namePtr) { - continue; - } - - std::string goName; - if (!ReadCString(*acc, namePtr, goName)) { - continue; - } - - // Priority 1: "Main Camera" - if (goName == "Main Camera" && !mainCameraNative) { - mainCameraNative = entry.nativeComponent; - mainCameraManaged = entry.managedComponent; - break; // Highest priority, return immediately - } - - // Priority 2: "Camera Top" - if (goName == "Camera Top" && !cameraTopNative) { - cameraTopNative = entry.nativeComponent; - cameraTopManaged = entry.managedComponent; - continue; - } - - // Priority 3: First camera (as fallback) - if (!firstEnabledNative) { - firstEnabledNative = entry.nativeComponent; - firstEnabledManaged = entry.managedComponent; - } - } - - // Return by priority - if (mainCameraNative) { - outNativeCamera = mainCameraNative; - outManagedCamera = mainCameraManaged; - return true; - } - if (cameraTopNative) { - outNativeCamera = cameraTopNative; - outManagedCamera = cameraTopManaged; - return true; - } - if (firstEnabledNative) { - outNativeCamera = firstEnabledNative; - outManagedCamera = firstEnabledManaged; - return true; } return false; diff --git a/External/GameObjectManager/Managed/ManagedObject.hpp b/External/GameObjectManager/Managed/ManagedObject.hpp index 90463ee..03fad85 100644 --- a/External/GameObjectManager/Managed/ManagedObject.hpp +++ b/External/GameObjectManager/Managed/ManagedObject.hpp @@ -51,18 +51,4 @@ struct ManagedObject { } }; -// Helper: Convert managed address to native -inline bool Managed_GetNative(std::uintptr_t managedAddress, std::uintptr_t& outNative) { - outNative = 0; - if (!managedAddress) return false; - return ReadPtrGlobal(managedAddress + 0x10u, outNative) && outNative != 0; -} - -// Helper: Convert native address to managed -inline bool Native_GetManaged(std::uintptr_t nativeAddress, std::uintptr_t& outManaged) { - outManaged = 0; - if (!nativeAddress) return false; - return ReadPtrGlobal(nativeAddress + 0x28u, outManaged) && outManaged != 0; -} - } // namespace UnityExternal diff --git a/External/GameObjectManager/Native/NativeGameObject.hpp b/External/GameObjectManager/Native/NativeGameObject.hpp index fa7a9a2..9fddfbe 100644 --- a/External/GameObjectManager/Native/NativeGameObject.hpp +++ b/External/GameObjectManager/Native/NativeGameObject.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "../../Core/UnityExternalMemory.hpp" #include "../../Core/UnityExternalMemoryConfig.hpp" @@ -44,6 +45,34 @@ struct NativeGameObject { return ReadInt32Global(address + 0x40u, outCount); } + // Get tag (GameObject + 0x54, int32/word) + bool GetTag(std::int32_t& outTag) const { + outTag = 0; + if (!address) return false; + return ReadInt32Global(address + 0x54u, outTag); + } + + // Get all component type IDs (from component pool entries) + bool GetComponentTypeIds(std::vector& outTypeIds) const { + outTypeIds.clear(); + if (!address) return false; + + std::uintptr_t pool = 0; + if (!GetComponentPool(pool) || !pool) return false; + + std::int32_t count = 0; + if (!GetComponentCount(count) || count <= 0 || count > 1024) return false; + + outTypeIds.reserve(static_cast(count)); + for (int i = 0; i < count; ++i) { + std::uintptr_t entry = pool + static_cast(i) * 16u; + std::int32_t typeId = 0; + if (!ReadInt32Global(entry, typeId)) continue; + outTypeIds.push_back(typeId); + } + return true; + } + // Get name (+0x60 -> string pointer) std::string GetName() const { const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); diff --git a/External/GameObjectManager/UnityExternalGOM.hpp b/External/GameObjectManager/UnityExternalGOM.hpp index cb49bf0..fd08af8 100644 --- a/External/GameObjectManager/UnityExternalGOM.hpp +++ b/External/GameObjectManager/UnityExternalGOM.hpp @@ -1,10 +1,13 @@ #pragma once - + #include #include #include +#include +#include #include "../Core/UnityExternalMemory.hpp" +#include "../Core/UnityExternalMemoryConfig.hpp" #include "../Core/UnityExternalTypes.hpp" namespace UnityExternal { @@ -28,6 +31,8 @@ class GOMWalker { bool ReadManagerFromGlobal(std::uintptr_t gomGlobalAddress, std::uintptr_t& managerAddress) const; bool EnumerateGameObjects(std::uintptr_t managerAddress, std::vector& out) const; bool EnumerateGameObjectsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const; + bool EnumerateGameObjectsParallel(std::uintptr_t managerAddress, std::vector& out, std::int32_t maxThreads = 0) const; + bool EnumerateGameObjectsFromGlobalParallel(std::uintptr_t gomGlobalAddress, std::vector& out, std::int32_t maxThreads = 0) const; bool EnumerateComponents(std::uintptr_t managerAddress, std::vector& out) const; bool EnumerateComponentsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const; @@ -51,63 +56,218 @@ inline bool GOMWalker::EnumerateGameObjects(std::uintptr_t managerAddress, std:: if (!managerAddress) { return false; } + + // Newer Unity: GameObjectManager is a hash_map keyed by PersistentTypeID. + // Layout (from IDA): + // managerAddress -> base_hash_map + // [0x00] buckets pointer + // [0x08] bucketCount (int) + // Each bucket is 24 bytes; bucket+0x10 is listHead, nodes are a linked list: + // node+0x08 -> next, node+0x10 -> nativeGameObject + // We traverse all buckets to collect all GameObjects. + + std::uintptr_t buckets = 0; + if (!ReadPtr(mem_, managerAddress + 0x0, buckets) || !buckets) { + return false; + } - std::uintptr_t listHead = 0; - if (!ReadPtr(mem_, managerAddress + 0x28, listHead) || !listHead) { + std::int32_t bucketCount = 0; + if (!ReadInt32(mem_, managerAddress + 0x8, bucketCount) || bucketCount <= 0 || bucketCount > 0x100000) { return false; } const std::size_t kMaxObjects = 1000000; - std::uintptr_t firstNode = listHead; - std::uintptr_t tail = 0; - ReadPtr(mem_, firstNode + 0x0, tail); + const std::uintptr_t bucketStride = 24; // 0x18 - std::uintptr_t node = firstNode; - for (std::size_t i = 0; node && i < kMaxObjects; ++i) { - std::uintptr_t nativeObject = 0; - std::uintptr_t managedObject = 0; - std::uintptr_t next = 0; + for (std::int32_t bi = 0; bi < bucketCount; ++bi) { + std::uintptr_t bucketPtr = buckets + static_cast(bi) * bucketStride; + std::uintptr_t listHead = 0; + if (!ReadPtr(mem_, bucketPtr + 0x10, listHead) || !listHead) { + continue; + } - if (!ReadPtr(mem_, node + 0x10, nativeObject)) { - return false; + std::uintptr_t node = 0; + if (!ReadPtr(mem_, listHead + 0x8, node) || !node) { + continue; } - if (nativeObject) { - if (!ReadPtr(mem_, nativeObject + 0x28, managedObject)) { - return false; + + for (std::size_t i = 0; node && i < kMaxObjects; ++i) { + std::uintptr_t nativeObject = 0; + std::uintptr_t managedObject = 0; + std::uintptr_t next = 0; + + if (!ReadPtr(mem_, node + 0x10, nativeObject)) { + break; + } + if (nativeObject) { + ReadPtr(mem_, nativeObject + 0x28, managedObject); } - } - if (nativeObject || managedObject) { - GameObjectEntry entry{}; - entry.node = node; - entry.nativeObject = nativeObject; - entry.managedObject = managedObject; - out.push_back(entry); - } + if (nativeObject || managedObject) { + GameObjectEntry entry{}; + entry.node = node; + entry.nativeObject = nativeObject; + entry.managedObject = managedObject; + out.push_back(entry); + } - if (tail && node == tail) { - break; + if (!ReadPtr(mem_, node + 0x8, next) || !next || next == listHead) { + break; + } + node = next; } + } - if (!ReadPtr(mem_, node + 0x8, next)) { - return false; - } - if (!next || next == firstNode) { - break; + return !out.empty(); +} + +inline bool GOMWalker::EnumerateGameObjectsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const { + std::uintptr_t managerAddress = 0; + if (!ReadManagerFromGlobal(gomGlobalAddress, managerAddress) || !managerAddress) { + return false; + } + return EnumerateGameObjects(managerAddress, out); +} + +inline bool GOMWalker::EnumerateGameObjectsParallel(std::uintptr_t managerAddress, + std::vector& out, + std::int32_t maxThreads) const +{ + out.clear(); + if (!managerAddress) { + return false; + } + + std::uintptr_t buckets = 0; + if (!ReadPtr(mem_, managerAddress + 0x0, buckets) || !buckets) { + return false; + } + + std::int32_t bucketCount = 0; + if (!ReadInt32(mem_, managerAddress + 0x8, bucketCount) || bucketCount <= 0 || bucketCount > 0x100000) { + return false; + } + + if (bucketCount == 1) { + return EnumerateGameObjects(managerAddress, out); + } + + unsigned int hwThreads = std::thread::hardware_concurrency(); + if (hwThreads == 0) { + hwThreads = 4; + } + + std::int32_t maxThreadLimit = static_cast(hwThreads); + if (maxThreads > 0 && maxThreads < maxThreadLimit) { + maxThreadLimit = maxThreads; + } + + std::int32_t threadCount = bucketCount; + if (threadCount > maxThreadLimit) { + threadCount = maxThreadLimit; + } + + if (threadCount <= 1) { + return EnumerateGameObjects(managerAddress, out); + } + + const std::uintptr_t bucketStride = 24; // 0x18 + const std::size_t kMaxObjects = 1000000; + + std::vector> threadResults; + threadResults.resize(static_cast(threadCount)); + + std::vector threads; + threads.reserve(static_cast(threadCount)); + + std::int32_t base = bucketCount / threadCount; + std::int32_t rem = bucketCount % threadCount; + + std::int32_t startBucket = 0; + for (std::int32_t ti = 0; ti < threadCount; ++ti) { + std::int32_t count = base + (ti < rem ? 1 : 0); + std::int32_t begin = startBucket; + std::int32_t end = begin + count; + + threads.emplace_back( + [this, buckets, begin, end, bucketStride, kMaxObjects, &threadResults, ti]() { + auto& local = threadResults[static_cast(ti)]; + + for (std::int32_t bi = begin; bi < end; ++bi) { + std::uintptr_t bucketPtr = buckets + static_cast(bi) * bucketStride; + std::uintptr_t listHead = 0; + if (!ReadPtr(this->mem_, bucketPtr + 0x10, listHead) || !listHead) { + continue; + } + + std::uintptr_t node = 0; + if (!ReadPtr(this->mem_, listHead + 0x8, node) || !node) { + continue; + } + + for (std::size_t i = 0; node && i < kMaxObjects; ++i) { + std::uintptr_t nativeObject = 0; + std::uintptr_t managedObject = 0; + std::uintptr_t next = 0; + + if (!ReadPtr(this->mem_, node + 0x10, nativeObject)) { + break; + } + if (nativeObject) { + ReadPtr(this->mem_, nativeObject + 0x28, managedObject); + } + + if (nativeObject || managedObject) { + GameObjectEntry entry{}; + entry.node = node; + entry.nativeObject = nativeObject; + entry.managedObject = managedObject; + local.push_back(entry); + } + + if (!ReadPtr(this->mem_, node + 0x8, next) || !next || next == listHead) { + break; + } + node = next; + } + } + }); + + startBucket = end; + } + + for (auto& t : threads) { + if (t.joinable()) { + t.join(); } + } + + std::size_t totalCount = 0; + for (const auto& v : threadResults) { + totalCount += v.size(); + } + + if (totalCount == 0) { + return false; + } - node = next; + out.reserve(totalCount); + for (auto& v : threadResults) { + out.insert(out.end(), v.begin(), v.end()); } return true; } -inline bool GOMWalker::EnumerateGameObjectsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const { +inline bool GOMWalker::EnumerateGameObjectsFromGlobalParallel(std::uintptr_t gomGlobalAddress, + std::vector& out, + std::int32_t maxThreads) const +{ std::uintptr_t managerAddress = 0; if (!ReadManagerFromGlobal(gomGlobalAddress, managerAddress) || !managerAddress) { return false; } - return EnumerateGameObjects(managerAddress, out); + return EnumerateGameObjectsParallel(managerAddress, out, maxThreads); } inline bool GOMWalker::EnumerateComponents(std::uintptr_t managerAddress, std::vector& out) const { @@ -149,10 +309,9 @@ inline bool GOMWalker::EnumerateComponents(std::uintptr_t managerAddress, std::v continue; } + // Don't skip managed=0, some built-in components may have null managed ptr std::uintptr_t managedComponent = 0; - if (!ReadPtr(mem_, nativeComponent + 0x28, managedComponent) || !managedComponent) { - continue; - } + ReadPtr(mem_, nativeComponent + 0x28, managedComponent); ComponentEntry entry{}; entry.nativeComponent = nativeComponent; @@ -172,4 +331,210 @@ inline bool GOMWalker::EnumerateComponentsFromGlobal(std::uintptr_t gomGlobalAdd return EnumerateComponents(managerAddress, out); } +// Find first GameObject with given tag using GOMWalker enumeration. +inline bool FindGameObjectThroughTag(const GOMWalker& walker, + std::uintptr_t gomGlobalAddress, + std::int32_t tag, + std::uintptr_t& outNativeObject, + std::uintptr_t& outManagedObject) +{ + outNativeObject = 0; + outManagedObject = 0; + + if (!gomGlobalAddress) { + return false; + } + + std::vector gameObjects; + if (!walker.EnumerateGameObjectsFromGlobal(gomGlobalAddress, gameObjects) || gameObjects.empty()) { + return false; + } + + for (const auto& go : gameObjects) { + if (!go.nativeObject) { + continue; + } + + std::uint16_t tagValue = 0; + if (!ReadValueGlobal(go.nativeObject + 0x54u, tagValue)) { + continue; + } + + if (static_cast(tagValue) == tag) { + outNativeObject = go.nativeObject; + outManagedObject = go.managedObject; + return true; + } + } + + return false; +} + +// Find first GameObject with given name using GOMWalker enumeration. +inline bool FindGameObjectThroughName(const GOMWalker& walker, + std::uintptr_t gomGlobalAddress, + const std::string& name, + std::uintptr_t& outNativeObject, + std::uintptr_t& outManagedObject) +{ + outNativeObject = 0; + outManagedObject = 0; + + if (!gomGlobalAddress) { + return false; + } + + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc) { + return false; + } + + std::vector gameObjects; + if (!walker.EnumerateGameObjectsFromGlobal(gomGlobalAddress, gameObjects) || gameObjects.empty()) { + return false; + } + + for (const auto& go : gameObjects) { + if (!go.nativeObject) { + continue; + } + + std::uintptr_t namePtr = 0; + if (!ReadPtrGlobal(go.nativeObject + 0x60u, namePtr) || !namePtr) { + continue; + } + + std::string goName; + if (!ReadCString(*acc, namePtr, goName)) { + continue; + } + + if (goName == name) { + outNativeObject = go.nativeObject; + outManagedObject = go.managedObject; + return true; + } + } + + return false; +} + +// Get component on a GameObject by type ID. +inline bool GetComponentThroughTypeId(std::uintptr_t gameObjectNative, + std::int32_t typeId, + std::uintptr_t& outNativeComponent, + std::uintptr_t& outManagedComponent) +{ + outNativeComponent = 0; + outManagedComponent = 0; + + if (!gameObjectNative) { + return false; + } + + std::uintptr_t pool = 0; + if (!ReadPtrGlobal(gameObjectNative + 0x30u, pool) || !pool) { + return false; + } + + std::int32_t count = 0; + if (!ReadInt32Global(gameObjectNative + 0x40u, count)) { + return false; + } + if (count <= 0 || count > 1024) { + return false; + } + + for (int i = 0; i < count; ++i) { + std::uintptr_t entryAddr = pool + static_cast(i) * 16u; + std::int32_t typeIdValue = 0; + if (!ReadInt32Global(entryAddr, typeIdValue)) { + continue; + } + + if (typeIdValue != typeId) { + continue; + } + + std::uintptr_t slotAddr = pool + 0x8u + static_cast(i) * 0x10u; + std::uintptr_t nativeComp = 0; + if (!ReadPtrGlobal(slotAddr, nativeComp) || !nativeComp) { + continue; + } + + std::uintptr_t managedComp = 0; + ReadPtrGlobal(nativeComp + 0x28u, managedComp); + + outNativeComponent = nativeComp; + outManagedComponent = managedComp; + return true; + } + + return false; +} + +// Get component on a GameObject by managed type name. +inline bool GetComponentThroughTypeName(std::uintptr_t gameObjectNative, + const std::string& typeName, + std::uintptr_t& outNativeComponent, + std::uintptr_t& outManagedComponent) +{ + outNativeComponent = 0; + outManagedComponent = 0; + + if (!gameObjectNative) { + return false; + } + + const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); + if (!acc) { + return false; + } + + std::uintptr_t pool = 0; + if (!ReadPtrGlobal(gameObjectNative + 0x30u, pool) || !pool) { + return false; + } + + std::int32_t count = 0; + if (!ReadInt32Global(gameObjectNative + 0x40u, count)) { + return false; + } + if (count <= 0 || count > 1024) { + return false; + } + + for (int i = 0; i < count; ++i) { + std::uintptr_t slotAddr = pool + 0x8u + static_cast(i) * 0x10u; + std::uintptr_t nativeComp = 0; + if (!ReadPtrGlobal(slotAddr, nativeComp) || !nativeComp) { + continue; + } + + std::uintptr_t managedComp = 0; + ReadPtrGlobal(nativeComp + 0x28u, managedComp); + if (!managedComp) { + continue; + } + + std::string compName; + std::uintptr_t vtable = 0; + std::uintptr_t monoClass = 0; + std::uintptr_t namePtr = 0; + if (ReadPtrGlobal(managedComp + 0x0u, vtable) && + ReadPtrGlobal(vtable + 0x0u, monoClass) && + ReadPtrGlobal(monoClass + 0x48u, namePtr)) { + ReadCString(*acc, namePtr, compName); + } + + if (compName == typeName) { + outNativeComponent = nativeComp; + outManagedComponent = managedComp; + return true; + } + } + + return false; +} + } // namespace UnityExternal diff --git a/External/README.md b/External/README.md new file mode 100644 index 0000000..184167e --- /dev/null +++ b/External/README.md @@ -0,0 +1,175 @@ +# UnityExternal + +Unity 游戏跨进程外挂库,纯外部内存读取,无需注入。 + +## 特性 + +- **跨进程内存读取**:基于 `ReadProcessMemory`,支持自定义驱动接口 +- **GOM 遍历**:枚举所有 GameObject / Component +- **Transform 世界坐标**:通过 Hierarchy 层级计算真实世界坐标 +- **相机 W2S**:读取相机矩阵,世界坐标转屏幕坐标 +- **支持 Mono / IL2CPP**:自动适配两种运行时的内存结构 + +> [!IMPORTANT] +> **需要手动传入 GOM 偏移** +> GameObjectManager 全局指针偏移因游戏/Unity版本不同而异,需自行逆向查找。 +> 格式:`UnityPlayer.dll + 偏移`,例如 `unityPlayerBase + 0x1AE8C50` + +## 依赖 + +需要手动添加 [GLM 库](https://github.com/g-truc/glm): + +```bash +git clone https://github.com/g-truc/glm.git +``` + +目录结构: +``` +项目根目录/ +├── glm/ +│ ├── glm.hpp +│ └── ... +└── External/ + └── ... +``` + +## 目录结构 + +``` +External/ +├── Core/ # 基础内存接口 +│ ├── UnityExternalMemory.hpp # IMemoryAccessor 接口 +│ ├── UnityExternalMemoryConfig.hpp # 全局访问器 + ReadPtrGlobal +│ └── UnityExternalTypes.hpp # RuntimeKind / TypeInfo / GetManagedType +│ +├── MemoryRead/ # 内存读取实现(可替换) +│ └── UnityExternalMemoryWinAPI.hpp # 默认 WinAPI 实现 +│ +├── GameObjectManager/ # GOM 遍历 + 原生结构 +│ ├── UnityExternalGOM.hpp # GOMWalker +│ ├── Managed/ManagedObject.hpp # 托管对象封装 +│ └── Native/ +│ ├── NativeGameObject.hpp +│ ├── NativeComponent.hpp +│ └── NativeTransform.hpp +│ +├── Camera/ # 相机 + W2S +│ ├── UnityExternalCamera.hpp # FindMainCamera / Camera_GetMatrix +│ └── UnityExternalWorldToScreen.hpp # WorldToScreenPoint +│ +├── Analysis/ # 内存结构分析文档 +│ ├── IL2CPP内存结构.txt +│ ├── MONO内存结构.txt +│ └── UnityExternalTransform_PosAlgorithm.txt +│ +└── ExternalResolve.hpp # 统一入口(include 这个即可) +``` + +## 快速开始 + +```cpp +#include "External/ExternalResolve.hpp" + +int main() { + // 1. 打开目标进程 + HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid); + + // 2. 创建内存访问器 + UnityExternal::WinAPIMemoryAccessor accessor(hProcess); + UnityExternal::SetGlobalMemoryAccessor(&accessor); + + // 3. 创建 GOM Walker + std::uintptr_t gomGlobal = unityPlayerBase + GOM_OFFSET; // 需自行查找偏移 + UnityExternal::GOMWalker walker(accessor, UnityExternal::RuntimeKind::Mono); + // 或 RuntimeKind::Il2Cpp + + // 4. 遍历所有 GameObject + std::vector gameObjects; + walker.EnumerateGameObjectsFromGlobal(gomGlobal, gameObjects); + + for (const auto& go : gameObjects) { + UnityExternal::NativeGameObject nativeGO(go.nativeObject); + std::string name = nativeGO.GetName(); + // ... + } + + // 5. 查找主相机 + std::uintptr_t camNative = 0, camManaged = 0; + UnityExternal::FindMainCamera(walker, gomGlobal, camNative, camManaged); + + // 6. 读取相机矩阵 + glm::mat4 camMatrix; + UnityExternal::Camera_GetMatrix(camNative, camMatrix); + + // 7. 获取 Transform 世界坐标 + UnityExternal::Vector3f worldPos; + UnityExternal::GetTransformWorldPosition(transformNative, worldPos); + + // 8. 世界坐标转屏幕坐标 + UnityExternal::ScreenRect screen{ 0, 0, 1920, 1080 }; + auto result = UnityExternal::WorldToScreenPoint(camMatrix, screen, + glm::vec3(worldPos.x, worldPos.y, worldPos.z)); + + if (result.visible) { + // 绘制 ESP ... + } + + CloseHandle(hProcess); + return 0; +} +``` + +## 自定义内存访问器 + +默认使用 `WinAPIMemoryAccessor`,可替换为驱动或其他实现: + +```cpp +class MyDriverAccessor : public UnityExternal::IMemoryAccessor { +public: + bool Read(std::uintptr_t address, void* buffer, std::size_t size) const override { + return MyDriver::ReadMemory(address, buffer, size); + } + bool Write(std::uintptr_t address, const void* buffer, std::size_t size) const override { + return MyDriver::WriteMemory(address, buffer, size); + } +}; + +MyDriverAccessor accessor; +UnityExternal::SetGlobalMemoryAccessor(&accessor); +``` + +## 关键偏移 + +### GOM 全局指针 + +需要在 `UnityPlayer.dll` 中查找 GameObjectManager 全局指针偏移,不同游戏/版本偏移不同。 + +### 内存结构 + +参考 `Analysis/` 目录下的文档: + +| 运行时 | 类名偏移 | +|--------|----------| +| **IL2CPP** | `managed+0x00 → klass+0x10 → name` | +| **Mono** | `managed+0x00 → vtable+0x00 → klass+0x48 → name` | + +### 原生结构偏移 + +| 结构 | 偏移 | 说明 | +|------|------|------| +| NativeGameObject | +0x30 | 组件池指针 | +| NativeGameObject | +0x40 | 组件数量 | +| NativeGameObject | +0x60 | 名称字符串指针 | +| NativeComponent | +0x28 | 托管组件指针 | +| NativeComponent | +0x30 | 原生 GameObject 指针 | +| NativeTransform | +0x38 | Hierarchy State 指针 | +| NativeTransform | +0x40 | Hierarchy 索引 | +| Camera | +0x100 | 视图投影矩阵 (4x4) | + +## 平台 + +- Windows x64 + +## 许可 + +MIT License From 01063bf9c17bf19ec7e93080249548cf3a8a6b35 Mon Sep 17 00:00:00 2001 From: Sh_Rei Date: Fri, 12 Dec 2025 16:25:30 +0800 Subject: [PATCH 9/9] Add files via upload MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修 --- External/Camera/UnityExternalCamera.hpp | 42 +-- External/Core/UnityExternalMemoryConfig.hpp | 9 +- .../GameObjectManager/UnityExternalGOM.hpp | 260 +++++------------- README.md | 35 +-- UnityResolve.GOM.hpp | 112 +++++++- 5 files changed, 204 insertions(+), 254 deletions(-) diff --git a/External/Camera/UnityExternalCamera.hpp b/External/Camera/UnityExternalCamera.hpp index 5470b8d..a0eada7 100644 --- a/External/Camera/UnityExternalCamera.hpp +++ b/External/Camera/UnityExternalCamera.hpp @@ -54,8 +54,8 @@ inline bool FindMainCamera(const GOMWalker& walker, outNativeCamera = 0; outManagedCamera = 0; - const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); - if (!acc || !gomGlobalAddress) return false; + if (!gomGlobalAddress) return false; + const IMemoryAccessor& acc = walker.Accessor(); std::uintptr_t mainGoNative = 0; std::uintptr_t mainGoManaged = 0; @@ -63,43 +63,19 @@ inline bool FindMainCamera(const GOMWalker& walker, return false; } - std::uintptr_t componentArray = 0; - if (!ReadPtrGlobal(mainGoNative + 0x30u, componentArray) || !componentArray) { + std::uintptr_t nativeComp = 0; + std::uintptr_t managedComp = 0; + if (!GetComponentThroughTypeName(mainGoNative, "Camera", nativeComp, managedComp, walker.GetRuntime(), acc)) { return false; } - std::int32_t componentCount = 0; - if (!ReadInt32Global(mainGoNative + 0x40u, componentCount) || componentCount <= 0 || componentCount > 1024) { + if (!IsComponentEnabled(nativeComp)) { return false; } - for (int c = 0; c < componentCount; ++c) - { - std::uintptr_t compEntryAddr = componentArray + static_cast(c) * 16u; - std::uintptr_t nativeComp = 0; - if (!ReadPtrGlobal(compEntryAddr + 0x8u, nativeComp) || !nativeComp) continue; - - std::uintptr_t managedComp = 0; - ReadPtrGlobal(nativeComp + 0x28u, managedComp); - if (!managedComp) continue; - - std::string typeName; - std::uintptr_t vtable = 0, monoClass = 0, namePtr = 0; - if (ReadPtrGlobal(managedComp + 0x0u, vtable) && - ReadPtrGlobal(vtable + 0x0u, monoClass) && - ReadPtrGlobal(monoClass + 0x48u, namePtr)) { - ReadCString(*acc, namePtr, typeName); - } - - if (typeName == "Camera") { - if (!IsComponentEnabled(nativeComp)) { - continue; - } - outNativeCamera = nativeComp; - outManagedCamera = managedComp; - return true; - } - } + outNativeCamera = nativeComp; + outManagedCamera = managedComp; + return true; return false; } diff --git a/External/Core/UnityExternalMemoryConfig.hpp b/External/Core/UnityExternalMemoryConfig.hpp index 9180dea..bbeb7ff 100644 --- a/External/Core/UnityExternalMemoryConfig.hpp +++ b/External/Core/UnityExternalMemoryConfig.hpp @@ -1,19 +1,20 @@ #pragma once #include +#include #include "UnityExternalMemory.hpp" namespace UnityExternal { -// Global memory accessor singleton -inline const IMemoryAccessor* g_memoryAccessor = nullptr; +// Global memory accessor singleton (atomic pointer for thread-safety) +inline std::atomic g_memoryAccessor{nullptr}; inline void SetGlobalMemoryAccessor(const IMemoryAccessor* accessor) { - g_memoryAccessor = accessor; + g_memoryAccessor.store(accessor, std::memory_order_release); } inline const IMemoryAccessor* GetGlobalMemoryAccessor() { - return g_memoryAccessor; + return g_memoryAccessor.load(std::memory_order_acquire); } // Global helper functions using the global accessor diff --git a/External/GameObjectManager/UnityExternalGOM.hpp b/External/GameObjectManager/UnityExternalGOM.hpp index fd08af8..85e38ec 100644 --- a/External/GameObjectManager/UnityExternalGOM.hpp +++ b/External/GameObjectManager/UnityExternalGOM.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include "../Core/UnityExternalMemory.hpp" #include "../Core/UnityExternalMemoryConfig.hpp" @@ -31,12 +30,11 @@ class GOMWalker { bool ReadManagerFromGlobal(std::uintptr_t gomGlobalAddress, std::uintptr_t& managerAddress) const; bool EnumerateGameObjects(std::uintptr_t managerAddress, std::vector& out) const; bool EnumerateGameObjectsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const; - bool EnumerateGameObjectsParallel(std::uintptr_t managerAddress, std::vector& out, std::int32_t maxThreads = 0) const; - bool EnumerateGameObjectsFromGlobalParallel(std::uintptr_t gomGlobalAddress, std::vector& out, std::int32_t maxThreads = 0) const; bool EnumerateComponents(std::uintptr_t managerAddress, std::vector& out) const; bool EnumerateComponentsFromGlobal(std::uintptr_t gomGlobalAddress, std::vector& out) const; RuntimeKind GetRuntime() const { return runtime_; } + const IMemoryAccessor& Accessor() const { return mem_; } private: const IMemoryAccessor& mem_; @@ -50,7 +48,6 @@ inline bool GOMWalker::ReadManagerFromGlobal(std::uintptr_t gomGlobalAddress, st } return ReadPtr(mem_, gomGlobalAddress, managerAddress); } - inline bool GOMWalker::EnumerateGameObjects(std::uintptr_t managerAddress, std::vector& out) const { out.clear(); if (!managerAddress) { @@ -79,6 +76,8 @@ inline bool GOMWalker::EnumerateGameObjects(std::uintptr_t managerAddress, std:: const std::size_t kMaxObjects = 1000000; const std::uintptr_t bucketStride = 24; // 0x18 + const IMemoryAccessor& acc = mem_; + for (std::int32_t bi = 0; bi < bucketCount; ++bi) { std::uintptr_t bucketPtr = buckets + static_cast(bi) * bucketStride; std::uintptr_t listHead = 0; @@ -91,6 +90,24 @@ inline bool GOMWalker::EnumerateGameObjects(std::uintptr_t managerAddress, std:: continue; } + bool isGameObjectBucket = true; + std::uintptr_t firstNative = 0; + std::uintptr_t firstManaged = 0; + if (ReadPtr(mem_, node + 0x10, firstNative) && firstNative) { + ReadPtr(mem_, firstNative + 0x28, firstManaged); + } + + if (firstManaged) { + TypeInfo info; + if (GetManagedType(runtime_, acc, firstManaged, info) && info.name != "GameObject") { + isGameObjectBucket = false; + } + } + + if (!isGameObjectBucket) { + continue; + } + for (std::size_t i = 0; node && i < kMaxObjects; ++i) { std::uintptr_t nativeObject = 0; std::uintptr_t managedObject = 0; @@ -129,147 +146,6 @@ inline bool GOMWalker::EnumerateGameObjectsFromGlobal(std::uintptr_t gomGlobalAd return EnumerateGameObjects(managerAddress, out); } -inline bool GOMWalker::EnumerateGameObjectsParallel(std::uintptr_t managerAddress, - std::vector& out, - std::int32_t maxThreads) const -{ - out.clear(); - if (!managerAddress) { - return false; - } - - std::uintptr_t buckets = 0; - if (!ReadPtr(mem_, managerAddress + 0x0, buckets) || !buckets) { - return false; - } - - std::int32_t bucketCount = 0; - if (!ReadInt32(mem_, managerAddress + 0x8, bucketCount) || bucketCount <= 0 || bucketCount > 0x100000) { - return false; - } - - if (bucketCount == 1) { - return EnumerateGameObjects(managerAddress, out); - } - - unsigned int hwThreads = std::thread::hardware_concurrency(); - if (hwThreads == 0) { - hwThreads = 4; - } - - std::int32_t maxThreadLimit = static_cast(hwThreads); - if (maxThreads > 0 && maxThreads < maxThreadLimit) { - maxThreadLimit = maxThreads; - } - - std::int32_t threadCount = bucketCount; - if (threadCount > maxThreadLimit) { - threadCount = maxThreadLimit; - } - - if (threadCount <= 1) { - return EnumerateGameObjects(managerAddress, out); - } - - const std::uintptr_t bucketStride = 24; // 0x18 - const std::size_t kMaxObjects = 1000000; - - std::vector> threadResults; - threadResults.resize(static_cast(threadCount)); - - std::vector threads; - threads.reserve(static_cast(threadCount)); - - std::int32_t base = bucketCount / threadCount; - std::int32_t rem = bucketCount % threadCount; - - std::int32_t startBucket = 0; - for (std::int32_t ti = 0; ti < threadCount; ++ti) { - std::int32_t count = base + (ti < rem ? 1 : 0); - std::int32_t begin = startBucket; - std::int32_t end = begin + count; - - threads.emplace_back( - [this, buckets, begin, end, bucketStride, kMaxObjects, &threadResults, ti]() { - auto& local = threadResults[static_cast(ti)]; - - for (std::int32_t bi = begin; bi < end; ++bi) { - std::uintptr_t bucketPtr = buckets + static_cast(bi) * bucketStride; - std::uintptr_t listHead = 0; - if (!ReadPtr(this->mem_, bucketPtr + 0x10, listHead) || !listHead) { - continue; - } - - std::uintptr_t node = 0; - if (!ReadPtr(this->mem_, listHead + 0x8, node) || !node) { - continue; - } - - for (std::size_t i = 0; node && i < kMaxObjects; ++i) { - std::uintptr_t nativeObject = 0; - std::uintptr_t managedObject = 0; - std::uintptr_t next = 0; - - if (!ReadPtr(this->mem_, node + 0x10, nativeObject)) { - break; - } - if (nativeObject) { - ReadPtr(this->mem_, nativeObject + 0x28, managedObject); - } - - if (nativeObject || managedObject) { - GameObjectEntry entry{}; - entry.node = node; - entry.nativeObject = nativeObject; - entry.managedObject = managedObject; - local.push_back(entry); - } - - if (!ReadPtr(this->mem_, node + 0x8, next) || !next || next == listHead) { - break; - } - node = next; - } - } - }); - - startBucket = end; - } - - for (auto& t : threads) { - if (t.joinable()) { - t.join(); - } - } - - std::size_t totalCount = 0; - for (const auto& v : threadResults) { - totalCount += v.size(); - } - - if (totalCount == 0) { - return false; - } - - out.reserve(totalCount); - for (auto& v : threadResults) { - out.insert(out.end(), v.begin(), v.end()); - } - - return true; -} - -inline bool GOMWalker::EnumerateGameObjectsFromGlobalParallel(std::uintptr_t gomGlobalAddress, - std::vector& out, - std::int32_t maxThreads) const -{ - std::uintptr_t managerAddress = 0; - if (!ReadManagerFromGlobal(gomGlobalAddress, managerAddress) || !managerAddress) { - return false; - } - return EnumerateGameObjectsParallel(managerAddress, out, maxThreads); -} - inline bool GOMWalker::EnumerateComponents(std::uintptr_t managerAddress, std::vector& out) const { out.clear(); @@ -295,7 +171,7 @@ inline bool GOMWalker::EnumerateComponents(std::uintptr_t managerAddress, std::v std::int32_t componentCount = 0; if (!ReadInt32(mem_, info.nativeObject + 0x40, componentCount)) { - return false; + continue; } if (componentCount <= 0 || componentCount > kMaxComponentsPerObject) { continue; @@ -341,26 +217,17 @@ inline bool FindGameObjectThroughTag(const GOMWalker& walker, outNativeObject = 0; outManagedObject = 0; - if (!gomGlobalAddress) { - return false; - } - std::vector gameObjects; - if (!walker.EnumerateGameObjectsFromGlobal(gomGlobalAddress, gameObjects) || gameObjects.empty()) { + if (!walker.EnumerateGameObjectsFromGlobal(gomGlobalAddress, gameObjects)) { return false; } for (const auto& go : gameObjects) { - if (!go.nativeObject) { - continue; - } + if (!go.nativeObject) continue; std::uint16_t tagValue = 0; - if (!ReadValueGlobal(go.nativeObject + 0x54u, tagValue)) { - continue; - } - - if (static_cast(tagValue) == tag) { + if (ReadValue(walker.Accessor(), go.nativeObject + 0x54u, tagValue) && + static_cast(tagValue) == tag) { outNativeObject = go.nativeObject; outManagedObject = go.managedObject; return true; @@ -380,14 +247,7 @@ inline bool FindGameObjectThroughName(const GOMWalker& walker, outNativeObject = 0; outManagedObject = 0; - if (!gomGlobalAddress) { - return false; - } - - const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); - if (!acc) { - return false; - } + const IMemoryAccessor& acc = walker.Accessor(); std::vector gameObjects; if (!walker.EnumerateGameObjectsFromGlobal(gomGlobalAddress, gameObjects) || gameObjects.empty()) { @@ -400,12 +260,12 @@ inline bool FindGameObjectThroughName(const GOMWalker& walker, } std::uintptr_t namePtr = 0; - if (!ReadPtrGlobal(go.nativeObject + 0x60u, namePtr) || !namePtr) { + if (!ReadPtr(acc, go.nativeObject + 0x60u, namePtr) || !namePtr) { continue; } std::string goName; - if (!ReadCString(*acc, namePtr, goName)) { + if (!ReadCString(acc, namePtr, goName)) { continue; } @@ -477,7 +337,9 @@ inline bool GetComponentThroughTypeId(std::uintptr_t gameObjectNative, inline bool GetComponentThroughTypeName(std::uintptr_t gameObjectNative, const std::string& typeName, std::uintptr_t& outNativeComponent, - std::uintptr_t& outManagedComponent) + std::uintptr_t& outManagedComponent, + RuntimeKind runtime, + const IMemoryAccessor& acc) { outNativeComponent = 0; outManagedComponent = 0; @@ -486,18 +348,13 @@ inline bool GetComponentThroughTypeName(std::uintptr_t gameObjectNative, return false; } - const IMemoryAccessor* acc = GetGlobalMemoryAccessor(); - if (!acc) { - return false; - } - std::uintptr_t pool = 0; - if (!ReadPtrGlobal(gameObjectNative + 0x30u, pool) || !pool) { + if (!ReadPtr(acc, gameObjectNative + 0x30u, pool) || !pool) { return false; } std::int32_t count = 0; - if (!ReadInt32Global(gameObjectNative + 0x40u, count)) { + if (!ReadInt32(acc, gameObjectNative + 0x40u, count)) { return false; } if (count <= 0 || count > 1024) { @@ -507,27 +364,54 @@ inline bool GetComponentThroughTypeName(std::uintptr_t gameObjectNative, for (int i = 0; i < count; ++i) { std::uintptr_t slotAddr = pool + 0x8u + static_cast(i) * 0x10u; std::uintptr_t nativeComp = 0; - if (!ReadPtrGlobal(slotAddr, nativeComp) || !nativeComp) { + if (!ReadPtr(acc, slotAddr, nativeComp) || !nativeComp) { continue; } std::uintptr_t managedComp = 0; - ReadPtrGlobal(nativeComp + 0x28u, managedComp); + ReadPtr(acc, nativeComp + 0x28u, managedComp); if (!managedComp) { continue; } - std::string compName; - std::uintptr_t vtable = 0; - std::uintptr_t monoClass = 0; - std::uintptr_t namePtr = 0; - if (ReadPtrGlobal(managedComp + 0x0u, vtable) && - ReadPtrGlobal(vtable + 0x0u, monoClass) && - ReadPtrGlobal(monoClass + 0x48u, namePtr)) { - ReadCString(*acc, namePtr, compName); + TypeInfo info; + if (GetManagedType(runtime, acc, managedComp, info) && info.name == typeName) { + outNativeComponent = nativeComp; + outManagedComponent = managedComp; + return true; } + } + + return false; +} + +inline bool FindGameObjectWithComponentThroughTypeName(const GOMWalker& walker, + std::uintptr_t gomGlobalAddress, + const std::string& typeName, + std::uintptr_t& outGameObjectNative, + std::uintptr_t& outNativeComponent, + std::uintptr_t& outManagedComponent) +{ + outGameObjectNative = 0; + outNativeComponent = 0; + outManagedComponent = 0; - if (compName == typeName) { + const IMemoryAccessor& acc = walker.Accessor(); + + std::vector gameObjects; + if (!walker.EnumerateGameObjectsFromGlobal(gomGlobalAddress, gameObjects) || gameObjects.empty()) { + return false; + } + + for (const auto& go : gameObjects) { + if (!go.nativeObject) { + continue; + } + + std::uintptr_t nativeComp = 0; + std::uintptr_t managedComp = 0; + if (GetComponentThroughTypeName(go.nativeObject, typeName, nativeComp, managedComp, walker.GetRuntime(), acc)) { + outGameObjectNative = go.nativeObject; outNativeComponent = nativeComp; outManagedComponent = managedComp; return true; diff --git a/README.md b/README.md index ef833a9..0a06839 100644 --- a/README.md +++ b/README.md @@ -90,32 +90,35 @@ External/ ```cpp #include "External/ExternalResolve.hpp" -// 1. 创建内存访问器 +// 0. 打开目标进程,创建内存访问器(可替换为自定义/驱动版) HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid); UnityExternal::WinAPIMemoryAccessor accessor(hProcess); UnityExternal::SetGlobalMemoryAccessor(&accessor); -// 2. 遍历所有 GameObject +// 1. 确定 GOM 全局指针(unityPlayerBase + 自行逆向的偏移) std::uintptr_t gomGlobal = unityPlayerBase + GOM_OFFSET; -UnityExternal::GOMWalker walker(accessor, UnityExternal::RuntimeKind::Mono); -std::vector gameObjects; -walker.EnumerateGameObjectsFromGlobal(gomGlobal, gameObjects); - -for (const auto& go : gameObjects) { - UnityExternal::NativeGameObject nativeGO(go.nativeObject); - std::string name = nativeGO.GetName(); - // ... -} +// 2. 创建 GOM Walker(Mono 或 Il2Cpp 任选其一,必要时都跑一遍) +UnityExternal::GOMWalker walker(accessor, UnityExternal::RuntimeKind::Mono); -// 3. 获取 Transform 世界坐标 -UnityExternal::Vector3f pos; -UnityExternal::GetTransformWorldPosition(transformNative, pos); +// 3. 查找主相机 +std::uintptr_t camNative = 0, camManaged = 0; +UnityExternal::FindMainCamera(walker, gomGlobal, camNative, camManaged); -// 4. 相机 W2S +// 4. 获取相机矩阵 + W2S glm::mat4 camMatrix; -UnityExternal::Camera_GetMatrix(cameraNative, camMatrix); +UnityExternal::Camera_GetMatrix(camNative, camMatrix); +UnityExternal::ScreenRect screen{0, 0, 1920, 1080}; +UnityExternal::Vector3f pos{/*world position*/}; auto result = UnityExternal::WorldToScreenPoint(camMatrix, screen, glm::vec3(pos.x, pos.y, pos.z)); + +// 5. 遍历所有 GameObject / 组件 +std::vector gameObjects; +walker.EnumerateGameObjectsFromGlobal(gomGlobal, gameObjects); + +std::uintptr_t goNative = 0, compNative = 0, compManaged = 0; +UnityExternal::FindGameObjectWithComponentThroughTypeName( + walker, gomGlobal, "Camera", goNative, compNative, compManaged); ``` ### 自定义内存访问器 diff --git a/UnityResolve.GOM.hpp b/UnityResolve.GOM.hpp index 5e2d48d..e19eb07 100644 --- a/UnityResolve.GOM.hpp +++ b/UnityResolve.GOM.hpp @@ -11,10 +11,18 @@ struct UnityGOMInfo { std::uintptr_t address; std::uintptr_t offset; + bool hasTypeIdBounds; + std::int32_t typeIdRange; + std::int32_t typeIdLower; + std::uintptr_t typeIdRangeAddr; + std::uintptr_t typeIdLowerAddr; }; namespace UnityResolveGOM { +inline std::uintptr_t SafeReadPtr(std::uintptr_t addr); +inline std::int32_t SafeReadInt32(std::uintptr_t addr); + namespace { constexpr std::size_t kUnityFunctionScanLen = 0x400; } @@ -193,9 +201,87 @@ inline std::uintptr_t FindFirstMovToModule(std::uintptr_t func, std::size_t maxL return 0; } +inline bool FindTypeIdBoundsInStep2(std::uintptr_t func, std::size_t maxLen, const ModuleRange& unityPlayer, + std::uintptr_t& typeIdRangeAddr, std::uintptr_t& typeIdLowerAddr) { + const auto* code = reinterpret_cast(func); + std::size_t retPos = maxLen; + for (std::size_t i = 0; i < maxLen; ++i) { + unsigned char op = code[i]; + if (op == 0xC3 || op == 0xC2) { + retPos = i; + break; + } + } + if (retPos == maxLen) { + return false; + } + + std::uintptr_t firstTarget = 0; + std::uintptr_t secondTarget = 0; + std::size_t prevEnd = static_cast(-1); + + for (std::size_t i = 0; i + 6 <= retPos; ) { + std::size_t idx = i; + bool hasRex = (code[idx] >= 0x40 && code[idx] <= 0x4F); + if (hasRex) { + ++idx; + } + + if (idx >= retPos || code[idx] != 0x8B) { + ++i; + continue; + } + + if (idx + 2 >= retPos) { + break; + } + + unsigned char modrm = code[idx + 1]; + if ((modrm & 0xC7) != 0x05) { + ++i; + continue; + } + + if (idx + 6 > retPos) { + break; + } + + auto disp = *reinterpret_cast(code + idx + 2); + std::size_t instLen = (hasRex ? 1 : 0) + 1 + 1 + 4; + std::uintptr_t nextRip = func + i + instLen; + std::uintptr_t target = nextRip + static_cast(disp); + + if (!IsInModule(unityPlayer, target)) { + i += instLen; + continue; + } + + if (!firstTarget) { + firstTarget = target; + prevEnd = i + instLen; + } else if (!secondTarget && i == prevEnd) { + secondTarget = target; + break; + } else { + firstTarget = target; + secondTarget = 0; + prevEnd = i + instLen; + } + + i += instLen; + } + + if (firstTarget && secondTarget) { + typeIdRangeAddr = firstTarget; + typeIdLowerAddr = secondTarget; + return true; + } + return false; +} + inline UnityGOMInfo FindGameObjectManagerImpl() { - UnityGOMInfo info{0, 0}; - + UnityGOMInfo info{0, 0, false, 0, 0, 0, 0}; + HMODULE il2cppModule = GetModuleHandleW(L"GameAssembly.dll"); HMODULE monoModule = nullptr; if (!il2cppModule) { @@ -248,30 +334,30 @@ inline UnityGOMInfo FindGameObjectManagerImpl() { if (!unityEntry) return info; - std::cout << "Entry[0]->UnityPlayer.dll+0x" - << std::hex << std::uppercase << (unityEntry - unityPlayer.base) - << std::dec << std::nouppercase << std::endl; - std::uintptr_t step2Func = FindFirstCallToUnityPlayer(unityEntry, kUnityFunctionScanLen, unityPlayer); if (!step2Func) return info; - std::cout << "Entry[1]->UnityPlayer.dll+0x" - << std::hex << std::uppercase << (step2Func - unityPlayer.base) - << std::dec << std::nouppercase << std::endl; + std::uintptr_t typeIdRangeAddr = 0; + std::uintptr_t typeIdLowerAddr = 0; + if (FindTypeIdBoundsInStep2(step2Func, kUnityFunctionScanLen, unityPlayer, typeIdRangeAddr, typeIdLowerAddr)) { + info.hasTypeIdBounds = true; + info.typeIdRange = SafeReadInt32(typeIdRangeAddr); + info.typeIdLower = SafeReadInt32(typeIdLowerAddr); + info.typeIdRangeAddr = typeIdRangeAddr; + info.typeIdLowerAddr = typeIdLowerAddr; + } std::uintptr_t unityGlobal = ResolveGOMFromUnityPlayerEntry(step2Func, unityPlayer); if (!unityGlobal) return info; info.address = unityGlobal; info.offset = unityGlobal - unityPlayer.base; - std::cout << "GameObjectManager->UnityPlayer.dll+0x" << std::hex << std::uppercase << info.offset - << std::dec << std::nouppercase << std::endl; return info; } inline UnityGOMInfo FindGameObjectManager() { static bool initialized = false; - static UnityGOMInfo cached{0, 0}; + static UnityGOMInfo cached{0, 0, false, 0, 0, 0, 0}; if (initialized) { return cached; @@ -280,7 +366,7 @@ inline UnityGOMInfo FindGameObjectManager() { __try { cached = FindGameObjectManagerImpl(); } __except (EXCEPTION_EXECUTE_HANDLER) { - cached = UnityGOMInfo{0, 0}; + cached = UnityGOMInfo{0, 0, false, 0, 0, 0, 0}; } initialized = true;