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..a0eada7 --- /dev/null +++ b/External/Camera/UnityExternalCamera.hpp @@ -0,0 +1,83 @@ +#pragma once + +#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 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; + + 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; +} + + +// 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, + std::uintptr_t& outManagedCamera) +{ + outNativeCamera = 0; + outManagedCamera = 0; + + if (!gomGlobalAddress) return false; + const IMemoryAccessor& acc = walker.Accessor(); + + std::uintptr_t mainGoNative = 0; + std::uintptr_t mainGoManaged = 0; + if (!FindGameObjectThroughTag(walker, gomGlobalAddress, 5, mainGoNative, mainGoManaged) || !mainGoNative) { + return false; + } + + std::uintptr_t nativeComp = 0; + std::uintptr_t managedComp = 0; + if (!GetComponentThroughTypeName(mainGoNative, "Camera", nativeComp, managedComp, walker.GetRuntime(), acc)) { + return false; + } + + if (!IsComponentEnabled(nativeComp)) { + return false; + } + + outNativeCamera = nativeComp; + outManagedCamera = managedComp; + 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..bbeb7ff --- /dev/null +++ b/External/Core/UnityExternalMemoryConfig.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include "UnityExternalMemory.hpp" + +namespace UnityExternal { + +// Global memory accessor singleton (atomic pointer for thread-safety) +inline std::atomic g_memoryAccessor{nullptr}; + +inline void SetGlobalMemoryAccessor(const IMemoryAccessor* accessor) { + g_memoryAccessor.store(accessor, std::memory_order_release); +} + +inline const IMemoryAccessor* GetGlobalMemoryAccessor() { + return g_memoryAccessor.load(std::memory_order_acquire); +} + +// 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..03fad85 --- /dev/null +++ b/External/GameObjectManager/Managed/ManagedObject.hpp @@ -0,0 +1,54 @@ +#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; + } +}; + +} // 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..9fddfbe --- /dev/null +++ b/External/GameObjectManager/Native/NativeGameObject.hpp @@ -0,0 +1,109 @@ +#pragma once + +#include +#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 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(); + 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..85e38ec --- /dev/null +++ b/External/GameObjectManager/UnityExternalGOM.hpp @@ -0,0 +1,424 @@ +#pragma once + +#include +#include +#include +#include + +#include "../Core/UnityExternalMemory.hpp" +#include "../Core/UnityExternalMemoryConfig.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_; } + const IMemoryAccessor& Accessor() const { return mem_; } + +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; + } + + // 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::int32_t bucketCount = 0; + if (!ReadInt32(mem_, managerAddress + 0x8, bucketCount) || bucketCount <= 0 || bucketCount > 0x100000) { + return false; + } + + 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; + if (!ReadPtr(mem_, bucketPtr + 0x10, listHead) || !listHead) { + continue; + } + + std::uintptr_t node = 0; + if (!ReadPtr(mem_, listHead + 0x8, node) || !node) { + 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; + 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 (!ReadPtr(mem_, node + 0x8, next) || !next || next == listHead) { + break; + } + node = next; + } + } + + 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::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)) { + continue; + } + 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; + } + + // Don't skip managed=0, some built-in components may have null managed ptr + std::uintptr_t managedComponent = 0; + ReadPtr(mem_, nativeComponent + 0x28, managedComponent); + + 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); +} + +// 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; + + std::vector gameObjects; + if (!walker.EnumerateGameObjectsFromGlobal(gomGlobalAddress, gameObjects)) { + return false; + } + + for (const auto& go : gameObjects) { + if (!go.nativeObject) continue; + + std::uint16_t tagValue = 0; + if (ReadValue(walker.Accessor(), go.nativeObject + 0x54u, tagValue) && + 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; + + 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 namePtr = 0; + if (!ReadPtr(acc, 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, + RuntimeKind runtime, + const IMemoryAccessor& acc) +{ + outNativeComponent = 0; + outManagedComponent = 0; + + if (!gameObjectNative) { + return false; + } + + std::uintptr_t pool = 0; + if (!ReadPtr(acc, gameObjectNative + 0x30u, pool) || !pool) { + return false; + } + + std::int32_t count = 0; + if (!ReadInt32(acc, 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 (!ReadPtr(acc, slotAddr, nativeComp) || !nativeComp) { + continue; + } + + std::uintptr_t managedComp = 0; + ReadPtr(acc, nativeComp + 0x28u, managedComp); + if (!managedComp) { + continue; + } + + 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; + + 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; + } + } + + return false; +} + +} // 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/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 diff --git a/README.md b/README.md index f44f34f..0a06839 100644 --- a/README.md +++ b/README.md @@ -1,246 +1,270 @@ -> [!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)

-
+## 目录 + +- [概述](#概述) +- [External 跨进程模块](#external-跨进程模块) +- [UnityResolve 内注入模块](#unityresolve-内注入模块) + +--- + +## 概述 + +| 模块 | 说明 | 平台 | +|------|------|------| +| **UnityResolve.hpp** | 内注入库,通过 DLL 注入调用托管 API | Windows / Android / Linux / iOS / HarmonyOS | +| **External/** | 跨进程外挂库,纯外部内存读取 | Windows | + +> [!WARNING] +> 如果编译器支持,请务必开启 SEH(结构化异常处理)。 -# 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)

-
- -#### 使用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) -> ``` +> 高版本 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 跨进程模块 + +独立于内注入的**纯外部内存读取模块**,适用于跨进程外挂开发。 + +### 依赖 + +需要手动添加 [GLM 库](https://github.com/g-truc/glm): + +```bash +# 克隆 GLM 到项目根目录 +git clone https://github.com/g-truc/glm.git +# 或下载 Release 解压到项目根目录 +``` + +确保目录结构为: +``` +项目根目录/ +├── 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 +│ +└── ExternalResolve.hpp # 统一入口 +``` + +### 快速开始 + +```cpp +#include "External/ExternalResolve.hpp" + +// 0. 打开目标进程,创建内存访问器(可替换为自定义/驱动版) +HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, pid); +UnityExternal::WinAPIMemoryAccessor accessor(hProcess); +UnityExternal::SetGlobalMemoryAccessor(&accessor); + +// 1. 确定 GOM 全局指针(unityPlayerBase + 自行逆向的偏移) +std::uintptr_t gomGlobal = unityPlayerBase + GOM_OFFSET; + +// 2. 创建 GOM Walker(Mono 或 Il2Cpp 任选其一,必要时都跑一遍) +UnityExternal::GOMWalker walker(accessor, UnityExternal::RuntimeKind::Mono); + +// 3. 查找主相机 +std::uintptr_t camNative = 0, camManaged = 0; +UnityExternal::FindMainCamera(walker, gomGlobal, camNative, camManaged); + +// 4. 获取相机矩阵 + W2S +glm::mat4 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); +``` + +### 自定义内存访问器 + +默认使用 `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 + +auto gos = UnityResolveGOM::EnumerateGameObjects(); +for (const auto& g : gos) { + // g.nativeObject / g.managedObject +} +``` + +> [!WARNING] +> 需开启 SEH,不同 Unity 版本可能存在兼容性差异。 + +### 更多用法 + +#### Mono 注入 +```cpp +// 仅 Mono 模式 +UnityResolve::AssemblyLoad assembly("./MonoCsharp.dll"); +``` + +#### 创建 C# 对象 +```cpp +// 字符串 +auto str = UnityResolve::UnityType::String::New("hello"); + +// 数组 +auto array = UnityResolve::UnityType::Array::New(pClass, 10); + +// 实例 +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 new file mode 100644 index 0000000..e19eb07 --- /dev/null +++ b/UnityResolve.GOM.hpp @@ -0,0 +1,545 @@ +#pragma once + +#if WINDOWS_MODE + +#include +#include +#pragma comment(lib, "Psapi.lib") +#include +#include + +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; +} + +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 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, false, 0, 0, 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::uintptr_t step2Func = FindFirstCallToUnityPlayer(unityEntry, kUnityFunctionScanLen, unityPlayer); + if (!step2Func) return info; + + 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; + return info; +} + +inline UnityGOMInfo FindGameObjectManager() { + static bool initialized = false; + static UnityGOMInfo cached{0, 0, false, 0, 0, 0, 0}; + + if (initialized) { + return cached; + } + + __try { + cached = FindGameObjectManagerImpl(); + } __except (EXCEPTION_EXECUTE_HANDLER) { + cached = UnityGOMInfo{0, 0, false, 0, 0, 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 + 0x40); + 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 c66e1b4..ce3741f 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)); } } }; @@ -1531,6 +1531,111 @@ 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; + }; + + 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; + char pad_0038[0x8]; + int componentCount; + char pad_0044[0x1C]; + void *objectName; + }; + + 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; + char pad_0038[0x8]; + int componentCount; + char pad_0044[0x1C]; + void *objectName; + }; + + 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, @@ -3389,5 +3494,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