1- using System . Diagnostics ;
21using System . Reflection ;
32using System . Runtime . CompilerServices ;
43using System . Runtime . InteropServices ;
@@ -14,6 +13,27 @@ namespace StArray.ModManager.Android.Native;
1413public static class Dobby
1514{
1615 private const string Lib = "modmanager" ;
16+ private static readonly object HookLock = new ( ) ;
17+ private static readonly Dictionary < HookKey , HookRecord > InstalledHooks = new ( ) ;
18+ private static readonly Dictionary < nint , HookChain > HookChains = new ( ) ;
19+ private static readonly Dictionary < nint , nint > DetourTargets = new ( ) ;
20+ private static readonly Dictionary < nint , HookRecord > LatestHooks = new ( ) ;
21+
22+ private readonly record struct HookKey ( nint Target , nint Detour ) ;
23+
24+ private sealed class HookChain ( nint target )
25+ {
26+ public nint Target { get ; } = target ;
27+ public nint Head { get ; set ; }
28+ public List < HookRecord > Layers { get ; } = new ( ) ;
29+ }
30+
31+ private sealed record HookRecord (
32+ nint Target ,
33+ nint PatchPoint ,
34+ nint Detour ,
35+ nint Origin ,
36+ string Owner ) ;
1737
1838 // ========================================================================
1939 // Native externs
@@ -24,8 +44,116 @@ public static class Dobby
2444 /// <param name="replace">替换函数地址(nint 指向 delegate 或函数指针)</param>
2545 /// <param name="origin">[out] 原函数指针(用于调用原逻辑)</param>
2646 /// <returns>0 = 成功,非 0 = 失败</returns>
27- [ DllImport ( Lib , EntryPoint = "modmanager_dobby_hook" ) ]
28- public static extern int Hook ( nint address , nint replace , out nint origin ) ;
47+ [ DllImport ( Lib , EntryPoint = "modmanager_dobby_hook" , CallingConvention = CallingConvention . Cdecl ) ]
48+ private static extern int InstallNativeHook ( nint address , nint replace , out nint origin ) ;
49+
50+ /// <summary>
51+ /// 安装 inline hook,并保持旧版 API 兼容。
52+ /// 同一目标的后续 detour 会安装到上一层 detour 上,形成托管侧维护的
53+ /// Hook 链,不依赖新版本 native HookBroker 导出。
54+ /// </summary>
55+ public static int Hook ( nint address , nint replace , out nint origin )
56+ => Hook ( address , replace , out origin , "Dobby.Hook" ) ;
57+
58+ /// <summary>安装带 owner 诊断信息的 Hook。</summary>
59+ public static int Hook ( nint address , nint replace , out nint origin , string ? owner )
60+ {
61+ origin = nint . Zero ;
62+ if ( address == nint . Zero || replace == nint . Zero )
63+ return - 1 ;
64+ if ( address == replace )
65+ return - 2 ;
66+
67+ lock ( HookLock )
68+ {
69+ var key = new HookKey ( address , replace ) ;
70+ if ( InstalledHooks . TryGetValue ( key , out var existing ) )
71+ {
72+ origin = existing . Origin ;
73+ return 0 ;
74+ }
75+
76+ // A detour already used as another chain's layer is a patched
77+ // entry point, not an independent target. Patching it as a new
78+ // root would make the two managed registries disagree.
79+ if ( DetourTargets . ContainsKey ( address ) )
80+ return - 3 ;
81+
82+ if ( DetourTargets . TryGetValue ( replace , out var boundTarget ) &&
83+ boundTarget != address )
84+ return - 3 ;
85+
86+ HookChains . TryGetValue ( address , out var chain ) ;
87+ var patchPoint = chain ? . Head ?? address ;
88+ if ( patchPoint == nint . Zero || patchPoint == replace )
89+ return - 2 ;
90+
91+ var result = InstallNativeHook ( patchPoint , replace , out origin ) ;
92+ if ( result != 0 )
93+ {
94+ origin = nint . Zero ;
95+ return result ;
96+ }
97+
98+ // A successful native hook must always provide a continuation. Do
99+ // not publish an incomplete layer to the process-wide registry.
100+ if ( origin == nint . Zero )
101+ {
102+ RemoveNativeHook ( patchPoint ) ;
103+ return - 4 ;
104+ }
105+
106+ chain ??= new HookChain ( address ) ;
107+ if ( ! HookChains . ContainsKey ( address ) )
108+ HookChains . Add ( address , chain ) ;
109+
110+ var record = new HookRecord (
111+ address ,
112+ patchPoint ,
113+ replace ,
114+ origin ,
115+ string . IsNullOrWhiteSpace ( owner ) ? "unknown" : owner ) ;
116+ chain . Head = replace ;
117+ chain . Layers . Add ( record ) ;
118+ InstalledHooks [ key ] = record ;
119+ DetourTargets [ replace ] = address ;
120+ LatestHooks [ address ] = record ;
121+ return 0 ;
122+ }
123+ }
124+
125+ /// <summary>获取目标地址最近安装的 Hook 层,供诊断使用。</summary>
126+ public static bool TryGetInstalledHook (
127+ nint address ,
128+ out string owner ,
129+ out nint detour ,
130+ out nint origin )
131+ {
132+ lock ( HookLock )
133+ {
134+ if ( LatestHooks . TryGetValue ( address , out var existing ) )
135+ {
136+ owner = existing . Owner ;
137+ detour = existing . Detour ;
138+ origin = existing . Origin ;
139+ return true ;
140+ }
141+ }
142+
143+ owner = string . Empty ;
144+ detour = nint . Zero ;
145+ origin = nint . Zero ;
146+ return false ;
147+ }
148+
149+ /// <summary>获取 C# Hook 链的层数。</summary>
150+ public static int GetLayerCount ( nint address )
151+ {
152+ lock ( HookLock )
153+ return HookChains . TryGetValue ( address , out var chain )
154+ ? chain . Layers . Count
155+ : 0 ;
156+ }
29157
30158 /// <summary>
31159 /// 安装 inline hook — 传入 C# Reflection MethodInfo。
@@ -45,7 +173,10 @@ public static int Hook(nint address, MethodInfo replaceMethod, out nint origin)
45173 // 强制 JIT 编译该方法
46174 RuntimeHelpers . PrepareMethod ( replaceMethod . MethodHandle ) ;
47175 nint replacePtr = replaceMethod . MethodHandle . GetFunctionPointer ( ) ;
48- return Hook ( address , replacePtr , out origin ) ;
176+ var owner = replaceMethod . DeclaringType == null
177+ ? replaceMethod . Name
178+ : replaceMethod . DeclaringType . FullName + "." + replaceMethod . Name ;
179+ return Hook ( address , replacePtr , out origin , owner ) ;
49180 }
50181
51182 /// <summary>安装动态指令插桩。</summary>
@@ -58,8 +189,41 @@ public static int Hook(nint address, MethodInfo replaceMethod, out nint origin)
58189 /// <summary>移除 hook 并恢复原函数。</summary>
59190 /// <param name="address">被 hook 的函数地址</param>
60191 /// <returns>0 = 成功</returns>
61- [ DllImport ( Lib , EntryPoint = "modmanager_dobby_destroy" ) ]
62- public static extern int Destroy ( nint address ) ;
192+ [ DllImport ( Lib , EntryPoint = "modmanager_dobby_destroy" , CallingConvention = CallingConvention . Cdecl ) ]
193+ private static extern int RemoveNativeHook ( nint address ) ;
194+
195+ /// <summary>
196+ /// 移除单层 Hook。包含多层的链不能安全地从中间拆除,因此保留到进程结束。
197+ /// </summary>
198+ public static int Destroy ( nint address )
199+ {
200+ lock ( HookLock )
201+ {
202+ if ( HookChains . TryGetValue ( address , out var chain ) && chain . Layers . Count > 1 )
203+ return - 5 ;
204+ if ( DetourTargets . ContainsKey ( address ) )
205+ return - 5 ;
206+
207+ // Keep the native operation inside the same lock as registry
208+ // updates so a concurrent install cannot attach to a hook while
209+ // it is being restored.
210+ var result = RemoveNativeHook ( address ) ;
211+ if ( result != 0 )
212+ return result ;
213+
214+ if ( ! HookChains . Remove ( address , out var removedChain ) )
215+ return result ;
216+
217+ foreach ( var layer in removedChain . Layers )
218+ {
219+ InstalledHooks . Remove ( new HookKey ( layer . Target , layer . Detour ) ) ;
220+ DetourTargets . Remove ( layer . Detour ) ;
221+ }
222+ LatestHooks . Remove ( address ) ;
223+
224+ return result ;
225+ }
226+ }
63227
64228 /// <summary>按动态库名和符号名解析函数地址。</summary>
65229 /// <param name="imageName">动态库名,如 "libil2cpp.so"</param>
0 commit comments