11using Mono . Cecil ;
22using Mono . Cecil . Cil ;
3+ using MonoMod . Utils ;
4+ using NuGet . Packaging ;
35using OTAPI . UnifiedServerProcess . Commons ;
46using OTAPI . UnifiedServerProcess . Core . Patching ;
57using OTAPI . UnifiedServerProcess . Core . Patching . DataModels ;
810using System ;
911using System . Collections . Generic ;
1012using System . Data ;
11- using System . Diagnostics . CodeAnalysis ;
1213using System . Linq ;
1314
1415namespace OTAPI . UnifiedServerProcess . Core . FunctionalFeatures
@@ -21,7 +22,6 @@ public static bool AdjustMethodReferences(
2122 PatcherArguments arguments ,
2223 ContextBoundMethodMap contextMethodMap ,
2324 ref MethodReference methodRefToAdjust ,
24- [ NotNullWhen ( true ) ]
2525 out MethodDefinition ? contextBoundMethod ,
2626 out MethodReference originalMethodRef ,
2727 out ContextTypeData ? contextProvider ) {
@@ -30,12 +30,68 @@ public static bool AdjustMethodReferences(
3030 contextProvider = null ;
3131 var calleeId = methodRefToAdjust . GetIdentifier ( ) ;
3232
33+ // Handle delegate-injected context parameters for Action.Invoke / Action.BeginInvoke
34+ if ( ( methodRefToAdjust . Name == nameof ( Action . Invoke ) || methodRefToAdjust . Name == nameof ( Action . BeginInvoke ) )
35+ && PatchingCommon . IsDelegateInjectedCtxParam ( methodRefToAdjust . DeclaringType ) ) {
36+
37+ // Resolve the delegate method definition
38+ var delegateInvokeDef = methodRefToAdjust . DeclaringType . Resolve ( ) . GetMethod ( methodRefToAdjust . Name ) ;
39+
40+ // Skip adjustment if parameter count matches (already context-aware)
41+ if ( delegateInvokeDef . Parameters . Count == methodRefToAdjust . Parameters . Count ) {
42+ contextBoundMethod = null ;
43+ return false ;
44+ }
45+
46+ // Preserve original method reference for flow analysis
47+ originalMethodRef = PatchingCommon . GetVanillaMethodRef (
48+ arguments . RootContextDef ,
49+ arguments . ContextTypes ,
50+ methodRefToAdjust ) ;
51+
52+ // Insert context parameter based on delegate definition
53+ if ( delegateInvokeDef . Parameters [ 0 ] . ParameterType is GenericParameter genericParameter ) {
54+ // Map generic parameter from the delegate type
55+ var genericOwner = ( ( GenericInstanceType ) methodRefToAdjust . DeclaringType ) . ElementType ;
56+
57+ var duplicate = new MethodReference ( methodRefToAdjust . Name , methodRefToAdjust . ReturnType , methodRefToAdjust . DeclaringType ) {
58+ HasThis = methodRefToAdjust . HasThis ,
59+ } ;
60+ duplicate . Parameters . Add ( new ParameterDefinition ( genericOwner . GenericParameters [ genericParameter . Position ] ) ) ;
61+ duplicate . Parameters . AddRange ( methodRefToAdjust . Parameters . Select ( p => p . Clone ( ) ) ) ;
62+
63+ methodRefToAdjust = duplicate ;
64+ contextBoundMethod = null ;
65+ return true ;
66+ }
67+ else if ( delegateInvokeDef . Parameters [ 0 ] . ParameterType . FullName is Constants . RootContextFullName ) {
68+ // Insert root context type
69+
70+ var duplicate = new MethodReference ( methodRefToAdjust . Name , methodRefToAdjust . ReturnType , methodRefToAdjust . DeclaringType ) {
71+ HasThis = methodRefToAdjust . HasThis ,
72+ } ;
73+ duplicate . Parameters . Add ( new ParameterDefinition ( arguments . RootContextDef ) ) ;
74+ duplicate . Parameters . AddRange ( methodRefToAdjust . Parameters . Select ( p => p . Clone ( ) ) ) ;
75+
76+ methodRefToAdjust = duplicate ;
77+ contextBoundMethod = null ;
78+ return true ;
79+ }
80+ else {
81+ // Unexpected parameter type
82+ throw new Exception ( ) ;
83+ }
84+ }
85+
86+
3387 // Validate if the method reference points to an unmodified vanilla method through:
3488 // 1. Existence in original-context-bound mapping, OR
3589 // 2. Preservation of original state (unresolvable method indicates no patches)
3690 // Only valid candidates will be replaced with context-aware versions
3791 if ( ! contextMethodMap . originalToContextBound . TryGetValue ( calleeId , out contextBoundMethod ) ) {
38- // Check if method exists in tail assembly (indicates modified)
92+
93+ // If TryResolve() returns non-null, it means this MethodReference already points
94+ // to a modified context-bound method (caller IL already updated), so no further action is needed.
3995 if ( methodRefToAdjust . TryResolve ( ) is not null ) {
4096 return false ;
4197 }
@@ -85,7 +141,6 @@ public static void InjectContextParameterLoads(
85141 ref Instruction methodCallInstruction ,
86142 out Instruction insertedFirstInstr ,
87143 MethodDefinition modifyMethod ,
88- MethodDefinition contextBound ,
89144 MethodReference calleeRef ,
90145 MethodReference vanillaCalleeRef ,
91146 ContextTypeData ? contextTypeData ,
@@ -288,15 +343,15 @@ public static void AdjustConstructorLoadRoot(this IContextInjectFeature point, T
288343 if ( baseCtorCall is null && firstLoadRoot_shouldMoveCtorCallWhenNotNull is null ) {
289344 bool isNotInit = false ;
290345 if ( check . OpCode == OpCodes . Ldarg_1 ) {
291- foreach ( var usage in MonoModCommon . Stack . AnalyzeStackTopValueUsage ( ctor , check ) ) {
346+ foreach ( var usage in MonoModCommon . Stack . TraceStackValueConsumers ( ctor , check ) ) {
292347 if ( usage is not { OpCode . Code : Code . Call , Operand : MethodReference { Name : ".ctor" } } ) {
293348 isNotInit = true ;
294349 break ;
295350 }
296351 }
297352 }
298353 else if ( check . OpCode == OpCodes . Ldarg_0 ) {
299- foreach ( var usage in MonoModCommon . Stack . AnalyzeStackTopValueUsage ( ctor , check ) ) {
354+ foreach ( var usage in MonoModCommon . Stack . TraceStackValueConsumers ( ctor , check ) ) {
300355 if ( usage . OpCode != OpCodes . Stfld && usage is not { OpCode . Code : Code . Call , Operand : MethodReference { Name : ".ctor" } } ) {
301356 isNotInit = true ;
302357 break ;
@@ -379,7 +434,7 @@ static void TraceUsage(IContextInjectFeature feature, MethodDefinition method, H
379434
380435 while ( works . Count > 0 ) {
381436 var current = works . Pop ( ) ;
382- var usages = MonoModCommon . Stack . AnalyzeStackTopValueUsage ( method , current ) ;
437+ var usages = MonoModCommon . Stack . TraceStackValueConsumers ( method , current ) ;
383438 ExtractSources ( feature , method , checkInsts , checkLocals , usages ) ;
384439 foreach ( var usage in usages ) {
385440 if ( MonoModCommon . Stack . GetPushCount ( method . Body , usage ) > 0 ) {
@@ -404,7 +459,7 @@ static void TraceUsage(IContextInjectFeature feature, MethodDefinition method, H
404459 case Code . Ldloca_S :
405460 case Code . Ldloca :
406461 if ( ! checkInsts . Contains ( inst ) ) {
407- var usages = MonoModCommon . Stack . AnalyzeStackTopValueUsage ( method , inst ) ;
462+ var usages = MonoModCommon . Stack . TraceStackValueConsumers ( method , inst ) ;
408463 ExtractSources ( feature , method , checkInsts , checkLocals , usages ) ;
409464 checkInsts . Add ( inst ) ;
410465 }
0 commit comments