Skip to content
Open
57 changes: 57 additions & 0 deletions External/Analysis/IL2CPP内存结构.txt
Original file line number Diff line number Diff line change
@@ -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 命名空间
};
61 changes: 61 additions & 0 deletions External/Analysis/MONO内存结构.txt
Original file line number Diff line number Diff line change
@@ -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*
};
100 changes: 100 additions & 0 deletions External/Analysis/UnityExternalTransform_PosAlgorithm.txt
Original file line number Diff line number Diff line change
@@ -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` 作为世界坐标。
83 changes: 83 additions & 0 deletions External/Camera/UnityExternalCamera.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#pragma once

#include <cstdint>
#include <vector>
#include <string>
#include <algorithm>

#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
90 changes: 90 additions & 0 deletions External/Camera/UnityExternalWorldToScreen.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#pragma once

#include <cstdint>

#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
Loading