Skip to content

Commit af2a834

Browse files
StArraySharpStArray
authored andcommitted
feat(runtime): 增强Runtime和Hook功能,支持更多运行时场景
- Runtime检测支持基于文件存在判断IL2CPP和Mono后端 - MonoDomain新增线程附加和分离方法,优化异步执行安全 - MonoObjectUnbox增加异常捕获,避免拆箱失败导致崩溃 - RuntimeArray和RuntimeObject访问中增加Mono线程附加保护 - HookAttributes添加CallingConvention属性支持调用约定配置 - MinHook新增目标地址为0检测及异常捕获,提升Hook稳定性和错误日志 - ModManagerUI支持F10键切换管理器显示,优化前后景渲染 - Windows平台设置Il2Cpp库路径,保障本地环境支持 - HookGenerator修正枚举常量格式化,准确解析枚举成员名 - 更新README,完善项目结构说明便于新手理解
1 parent 0ad1e84 commit af2a834

14 files changed

Lines changed: 253 additions & 69 deletions

File tree

‎README.md‎

Lines changed: 118 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,61 @@ Android IL2CPP Unity mod manager with CoreCLR runtime embedding and ImGui overla
1313
## Project Structure
1414

1515
```
16-
StArray.ModManager/ C# mod manager (.NET 10)
17-
Managed.cs CoreCLR entry – Logger桥接 → 扫描Mod → 启动ImGui
18-
Il2Cpp/ IL2CPP 内部类型 C# 翻译
19-
Core.cs / Types.cs Vector2/3/4, Quaternion, Il2CppString, Il2CppArray<T> 等
20-
Reflection.cs Il2CppAssembly, Il2CppClass, Il2CppMethod, Il2CppField
21-
Unity.cs UnityObject, Component, Transform, GameObject, Camera
22-
Coroutine.cs
23-
Inspector/ ImGui 自动检查器 + 设置特性([ModSettingRange] 等)
16+
StArray.ModManager/ C# core library (.NET 10)
17+
Behaviours/ 游戏生命周期行为
18+
BehaviourManager.cs 行为调度器
19+
GameBehaviour.cs 行为基类
20+
Il2Cpp/ IL2CPP 运行时反射实现
21+
Domain.cs IL2CPP 域
22+
Il2CppFunctions.cs IL2CPP 原生函数导入
23+
Il2CppReflection.cs Assembly/Class/Method/Field 反射
24+
Inspector/ ImGui 自动检查器 + 设置特性
25+
ModInspector.cs 检查器入口
26+
ModInspector.Build.cs 配置面板构建
27+
ModInspector.Draw.cs 字段绘制
28+
ModSettingAttributes.cs 设置特性([ModSettingLabel], [ModSettingRange] 等)
2429
Manager/ 核心逻辑
25-
ModLoader.cs Mod 扫描/加载/卸载/状态管理
26-
ModManagerUI.cs ImGui 主面板 + 设置窗口 + Overlay 背景/前景渲染
27-
ModManagerConfig.cs 全局配置 JSON 持久化(STJ 源生成)
28-
Logger.cs 统一日志 → logcat + 文件双写
2930
Benchmark.cs 栈式嵌套计时
30-
Native/ 原生绑定(Dobby, DL, JNI, AndroidUtils, UnityResolve)
31-
Runtime/ Mod 接口
32-
IModPlugin.cs OnLoad/OnUnload/OnBackgroundGUI/OnForegroundGUI
33-
IModSettings.cs / IModSettingCustomDraw.cs
34-
UI/ ImGui EGL/Vulkan 渲染器 + 输入处理 + FA7 图标
31+
Logger.cs 统一日志
32+
ModEntry.cs Mod 条目数据模型
33+
ModManagerConfig.cs 全局配置 JSON 持久化(STJ 源生成)
34+
ModManagerJsonContext.cs STJ 序列化上下文
35+
ModManagerUI.cs ImGui 主面板 + 设置窗口
36+
ModManagerUI.Panels.cs 面板子模块
37+
Mono/ Mono 运行时反射实现
38+
Mono.cs Mono 域
39+
MonoDomain.cs Mono 域操作(Assembly 队列、Flush 等)
40+
MonoFunctions.cs Mono 原生函数导入
41+
MonoReflection.cs Assembly/Class/Method/Field 反射
42+
Resources/ 嵌入资源
43+
fa-solid-900.ttf FontAwesome 7 图标字体
44+
msyh.ttf 微软雅黑字体
45+
L10n.cs / Localization.* 本地化字符串
46+
Runtime/ Mod 框架接口
47+
HookAttributes.cs [UnmanagedHook] 特性
48+
HookHelper.cs Hook 注册工具
49+
IHook.cs Hook 抽象
50+
IModPlugin.cs Mod 入口(OnLoad/OnUnload/OnBackgroundGUI/OnForegroundGUI)
51+
IModSettings.cs / IModSettingCustomDraw.cs 设置接口
52+
ModLoader.cs Mod 扫描/加载/卸载/状态管理
53+
RuntimeAbstractions/ 运行时抽象层(屏蔽 IL2CPP/Mono 差异)
54+
IAppDomain.cs 域抽象
55+
IRuntimeAssembly.cs 程序集抽象
56+
IRuntimeClass.cs 类抽象
57+
IRuntimeField.cs / IRuntimeMethod.cs 字段/方法抽象
58+
IUnmanagedObject.cs UnmanagedObject 基类 + Wrap<T>()
59+
RuntimeArray.cs 强类型数组访问
60+
RuntimeBox.cs 装箱/拆箱
61+
RuntimeManager.cs 运行时选择(IL2CPP vs Mono)
62+
RuntimeObject.cs 对象字段/方法/属性通用操作
63+
RuntimeString.cs string 读写
64+
UnmanagedMemberAttribute.cs 字段/方法声明特性
65+
GraphicsDevice.cs GPU 设备抽象
66+
TestStubs/ 测试 Stub
67+
Transform.cs Unity Transform 测试 Stub
68+
UI/ ImGui 工具
69+
FontAwesome7.cs FA7 图标常量
70+
IImGuiRenderer.cs 渲染器接口
3571
3672
Android/library/ 原生库 (libmodmanager.so)
3773
src/main/cpp/core/ Dobby hook / CoreCLR 嵌入 / JNI helper / UnityResolve
@@ -56,6 +92,71 @@ Android/library/ 原生库 (libmodmanager.so)
5692
- **IImGuiRenderer** interface — swap between EGL, Vulkan backends
5793
- **CoreCLR args** — pass `string[]` from Java to managed `Entry(int, IntPtr)`
5894

95+
## API Reference
96+
97+
### Mod Entry Point
98+
99+
| Interface | Method | Description |
100+
|-----------|--------|-------------|
101+
| `IModPlugin` | `OnLoad()` | Called when mod is loaded |
102+
| | `OnUnload()` | Called when mod is unloaded |
103+
| | `OnBackgroundGUI(ImDrawListPtr)` | Draw behind ImGui windows (watermark) |
104+
| | `OnForegroundGUI(ImDrawListPtr)` | Draw above ImGui windows (HUD) |
105+
| `IModSettings` | `OnGui()` | Draw custom settings tab in ModManager |
106+
107+
### Hooks
108+
109+
| Attribute | Target | Description |
110+
|-----------|--------|-------------|
111+
| `[UnmanagedHook("dll", "class", "method")]` | `static bool/nvoid Method(nint thiz, ...)` | Hooks a game method, replaces it with your implementation. Define `MethodOriginal` for calling the original. |
112+
113+
### Stubs (Typed Game Object Wrappers)
114+
115+
| Base Class | Description |
116+
|------------|-------------|
117+
| `UnmanagedObject` | Wraps `nint` pointer, provides `Obj` field for runtime access. Subclass to type-safe game object access. |
118+
| `Wrap<T>(nint ptr)` | Returns a typed stub wrapping the given pointer. |
119+
120+
### Runtime Reflection
121+
122+
| Class | Key Methods | Description |
123+
|-------|-------------|-------------|
124+
| `RuntimeObject` | `GetField<T>(string)` / `SetField(string, T)` | Read/write instance fields |
125+
| | `Invoke<T>(string, params object[])` | Call instance methods |
126+
| | `GetProperty<T>(string)` / `SetProperty(string, T)` | Read/write properties |
127+
| `RuntimeArray<T>` | `new RuntimeArray<T>(nint ptr)` | Strongly-typed array wrapper |
128+
| | `.Length` / `[index]` | Array length and element access |
129+
| | `.DataPtr` | Direct pointer to array elements |
130+
| `RuntimeManager` | `GetDomain()` / `IsIl2Cpp` | Detect current runtime and get domain |
131+
| `IAppDomain` | `OpenAssembly(string)` | Open a loaded assembly |
132+
| `IRuntimeAssembly` | `GetClass(string ns, string name)` | Get a class by namespace + name |
133+
| `IRuntimeClass` | `GetMethod(string, int)` / `GetField(string)` | Get method/field for invocation |
134+
| `IRuntimeMethod` | `Invoke(nint thisPtr, params object[])` / `InvokeStatic()` | Call a method |
135+
| `IRuntimeField` | `GetValue<T>(nint)` / `SetValue(nint, T)` | Read/write a field |
136+
137+
### Settings Attributes
138+
139+
| Attribute | Parameters | Description |
140+
|-----------|------------|-------------|
141+
| `[ModSettingLabel("text")]` | `string` | Display label for a setting field |
142+
| `[ModSettingLabelSide(ModInspector.LabelSide)]` | `Left` / `Right` | Label position |
143+
| `[ModSettingRange(min, max)]` | `float, float` | Clamp numeric values to range |
144+
| `[ModSettingJson(Lines = N)]` | `int` | Multi-line JSON editor |
145+
146+
### Inspector
147+
148+
| Method | Description |
149+
|--------|-------------|
150+
| `ModInspector.Draw(object)` | Auto-generate UI for all `[ModSettingLabel]` fields on the given object |
151+
152+
### Utilities
153+
154+
| Class | Description |
155+
|-------|-------------|
156+
| `Logger` | `Info(string)`, `Warn(string)`, `Error(string)` — unified logging |
157+
| `HookHelper` | `Instance` — set to your hook implementation (`MinHook` etc.) |
158+
| `BehaviourManager` | Schedule actions on Unity lifecycle events (Update, LateUpdate, etc.) |
159+
59160
## Third-Party Libraries
60161

61162
| Library | License | Used In |

‎StArray.ModManager.Android/Managed.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Runtime.InteropServices;
55
using StArray.ModManager.Android.Native;
66
using StArray.ModManager.Android.UI;
7+
using StArray.ModManager.Il2Cpp;
78
using StArray.ModManager.Manager;
89
using StArray.ModManager.Runtime;
910

@@ -57,6 +58,7 @@ public static int Entry(int argc, IntPtr argv)
5758
AssemblyPath = Path.Combine(Path.GetDirectoryName(modsPath), "manager", typeof(Managed).Namespace + ".dll");
5859
if (!Directory.Exists(modsPath))
5960
Directory.CreateDirectory(modsPath);
61+
Il2CppFunctions.SetIl2CppLibraryPath("libil2cpp.so");
6062

6163
// 文件日志 → manager 根目录(与 mods/、runtime/ 同级)
6264
var rootDir = Path.GetDirectoryName(modsPath)!;

‎StArray.ModManager.SourceGenerator/HookGenerator.cs‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,13 @@ private sealed class HookInfo
9595
private static string FormatConstant(TypedConstant tc)
9696
{
9797
if (tc.Kind == TypedConstantKind.Enum)
98-
return $"{tc.Type!.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{tc.Value}";
98+
{
99+
var intVal = tc.Value;
100+
foreach (var m in tc.Type!.GetMembers())
101+
if (m is IFieldSymbol fs && fs.ConstantValue != null && fs.HasConstantValue && fs.ConstantValue.Equals(intVal))
102+
return $"{tc.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{fs.Name}";
103+
return $"{tc.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}.{tc.Value}";
104+
}
99105
if (tc.Kind == TypedConstantKind.Array)
100106
{
101107
var items = tc.Values.Select(FormatConstant);

‎StArray.ModManager.Windows/Managed.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ public static int Entry(int argc, IntPtr argv)
8484

8585
Il2CppFunctions.SetIl2CppLibraryPath(Path.Combine(AppContext.BaseDirectory,"..","..", "GameAssembly.dll"));
8686
// 检测运行时后端 (IL2CPP / Mono)
87-
RuntimeManager.Detect();
88-
Logger.Info("Runtime", $"Detected backend: {RuntimeManager.Backend}");
87+
var backend = RuntimeManager.Detect();
88+
Logger.Info("Runtime", $"Detected backend: {backend}");
8989

9090
// 检测图形设备类型
9191
//var renderer = GetGameRenderer();

‎StArray.ModManager.Windows/NativeInterop/MinHook.cs‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,34 @@ private void EnsureInit()
5858
public nint Hook(nint target, nint detour)
5959
{
6060
EnsureInit();
61+
if (target == 0)
62+
{
63+
Logger.Warn("MinHook", "Hook target is 0, skipping");
64+
return nint.Zero;
65+
}
6166
if (_hooks.TryGetValue(target, out var existing))
6267
return existing;
6368

64-
var s = _CreateHook(target, detour, out var original);
69+
// 确保目标内存可读可写
70+
Win32Native.VirtualProtect(target, (UIntPtr)32, 0x40 /* PAGE_EXECUTE_READWRITE */, out _);
71+
72+
Status s;
73+
nint original;
74+
try
75+
{
76+
s = _CreateHook(target, detour, out original);
77+
}
78+
catch (AccessViolationException ex)
79+
{
80+
Logger.Error("MinHook", $"MH_CreateHook access violation at target=0x{target:X}: {ex.Message}");
81+
return nint.Zero;
82+
}
83+
catch (Exception ex)
84+
{
85+
Logger.Error("MinHook", $"MH_CreateHook failed at target=0x{target:X}: {ex.GetType().Name}: {ex.Message}");
86+
return nint.Zero;
87+
}
88+
6589
Logger.Info("MinHook", $"MH_CreateHook(target=0x{target:X}, detour=0x{detour:X}): {s} ({(int)s}), original=0x{original:X}");
6690
if (s != Status.Ok)
6791
return nint.Zero;

‎StArray.ModManager.Windows/NativeInterop/Win32Native.cs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public static class Win32Native
2121
[DllImport("kernel32.dll")]
2222
public static extern nint GetStdHandle(int nStdHandle);
2323

24+
[DllImport("kernel32.dll")]
25+
public static extern bool VirtualProtect(nint lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
26+
2427
// ── user32.dll ────────────────────────────────────────────
2528

2629
[DllImport("user32.dll", CharSet = CharSet.Unicode)]

‎StArray.ModManager/Manager/ModManagerUI.cs‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using IconFonts;
77
using ImGuiNET;
88
using StArray.ModManager.Inspector;
9+
using StArray.ModManager.Mono;
910
using StArray.ModManager.Resources;
1011
using StArray.ModManager.Runtime;
1112
using StArray.ModManager.Behaviours;
@@ -30,6 +31,8 @@ public partial class ModManagerUI
3031

3132
private bool _configApplied;
3233
private float _lastFrameTime;
34+
private bool _autoEnabled;
35+
private bool _visible = true;
3336

3437
/// <summary>初始化 UI,加载配置并扫描 Mod</summary>
3538
public ModManagerUI(ModLoader modManager, string configDir)
@@ -42,7 +45,6 @@ public ModManagerUI(ModLoader modManager, string configDir)
4245
if (string.IsNullOrEmpty(_config.ModsDirectory))
4346
_config.ModsDirectory = _modManager.ModsDirectory;
4447
_modManager.ScanMods();
45-
AutoEnableMods();
4648
}
4749

4850
private void ApplyConfig()
@@ -117,19 +119,41 @@ private void OnLogMessage(Logger.Level level, string tag, string msg)
117119
/// </summary>
118120
public void Render()
119121
{
120-
if (!_configApplied)
121-
{
122-
ApplyConfig();
123-
_configApplied = true;
124-
}
122+
// F10 切换管理器隐藏/显示
123+
if (ImGui.IsKeyPressed(ImGuiKey.F10))
124+
_visible = !_visible;
125125

126-
// ── BehaviourManager: 处理增删 + OnStart/OnUpdate(窗口绘制之前) ──
126+
// BehaviourManager: 处理增删 + OnStart/OnUpdate(始终运行)
127127
var now = (float)ImGui.GetTime();
128128
var delta = _lastFrameTime > 0 ? now - _lastFrameTime : 1f / 60f;
129129
_lastFrameTime = now;
130130
BehaviourManager.ProcessPending();
131131
BehaviourManager.Update(delta);
132132

133+
if (!_visible)
134+
{
135+
// 前景层:Mod 的 HUD 仍需绘制
136+
var fgList = ImGui.GetForegroundDrawList();
137+
foreach (var mod in _modManager.Mods)
138+
{
139+
if (mod is { LoadState: ModLoadState.Loaded, PluginInstance: not null })
140+
mod.PluginInstance.OnForegroundGUI(fgList);
141+
}
142+
return;
143+
}
144+
145+
if (!_configApplied)
146+
{
147+
ApplyConfig();
148+
_configApplied = true;
149+
}
150+
151+
if (!_autoEnabled)
152+
{
153+
_autoEnabled = true;
154+
AutoEnableMods();
155+
}
156+
133157
// 背景层:每个 Mod 在 ImGui 窗口下方绘制
134158
var bgDrawList = ImGui.GetBackgroundDrawList();
135159
foreach (var mod in _modManager.Mods)

‎StArray.ModManager/Mono/MonoDomain.cs‎

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ namespace StArray.ModManager.Mono;
77
public unsafe class MonoDomain : IAppDomain
88
{
99
private static readonly ConcurrentDictionary<string, List<Action>> _pendingActions = new(StringComparer.OrdinalIgnoreCase);
10+
private static readonly ConcurrentQueue<Action> _deferredActions = new();
11+
private _MonoThread* _thread; // Changed from _MonoThread* _thread;
1012

1113
public nint Ptr { get; }
1214
public bool IsValid => Ptr != 0;
@@ -18,29 +20,13 @@ public unsafe class MonoDomain : IAppDomain
1820

1921
internal static void OnAssemblyLoad(string name, nint asm) => AssemblyLoad?.Invoke(name, asm);
2022

21-
/// <summary>注册待办:当指定程序集加载后执行回调</summary>
22-
public static void QueueForAssembly(string assemblyName, Action callback)
23-
{
24-
_pendingActions.AddOrUpdate(assemblyName,
25-
_ => [callback],
26-
(_, list) => { list.Add(callback); return list; });
27-
}
28-
29-
internal static void FlushPending(string assemblyName)
30-
{
31-
if (_pendingActions.TryRemove(assemblyName, out var callbacks))
32-
{
33-
foreach (var cb in callbacks)
34-
{
35-
try { cb(); }
36-
catch (Exception ex) { Logger.Warn("Mono", $"Pending action for '{assemblyName}' failed: {ex.Message}"); }
37-
}
38-
}
39-
}
40-
4123
static MonoDomain()
4224
{
4325
MonoFunctions.InstallAssemblyLoadHook();
26+
AssemblyLoad += (name, asm) =>
27+
{
28+
Logger.Debug("Mono", $"Assembly loaded: {name}");
29+
};
4430
}
4531

4632
public static MonoDomain? Current
@@ -93,4 +79,15 @@ public IReadOnlyList<IRuntimeAssembly> GetAssemblies()
9379
public nint NewString(string str) => MonoFunctions.MonoStringNew(Ptr, str);
9480

9581
public nint NewArray(nint elementClass, int length) => MonoFunctions.MonoArrayNew(Ptr, elementClass, (nuint)length);
82+
83+
public void ThreadAttach()
84+
{
85+
if (_thread != null) return;
86+
_thread = Methods.mono_thread_attach((_MonoDomain*)Ptr);
87+
}
88+
public void ThreadDetach()
89+
{
90+
Methods.mono_thread_detach(_thread);
91+
_thread = null;
92+
}
9693
}

0 commit comments

Comments
 (0)