44using System . Reflection ;
55using System . Reflection . Emit ;
66using System . Runtime . InteropServices ;
7- using System . Text ;
87using BepInEx . Logging ;
98using HarmonyLib ;
109using HarmonyLib . Public . Patching ;
11- using Mono . Cecil . Cil ;
1210using MonoMod . Cil ;
1311using MonoMod . RuntimeDetour ;
1412using MonoMod . Utils ;
1513using UnhollowerBaseLib ;
1614using UnhollowerBaseLib . Runtime ;
1715using UnhollowerBaseLib . Runtime . VersionSpecific . MethodInfo ;
18- using OpCode = System . Reflection . Emit . OpCode ;
19- using OpCodes = System . Reflection . Emit . OpCodes ;
2016using ValueType = Il2CppSystem . ValueType ;
2117using Void = Il2CppSystem . Void ;
2218
@@ -39,7 +35,6 @@ private static readonly MethodInfo ObjectBaseToPtrMethodInfo
3935 private static readonly MethodInfo ReportExceptionMethodInfo
4036 = AccessTools . Method ( typeof ( IL2CPPDetourMethodPatcher ) , nameof ( ReportException ) ) ;
4137
42-
4338 private static readonly ManualLogSource DetourLogger = Logger . CreateLogSource ( "Detour" ) ;
4439
4540 // Map each value type to correctly sized store opcode to prevent memory overwrite
@@ -100,7 +95,7 @@ private void Init()
10095
10196 // Get the native MethodInfo struct for the target method
10297 originalNativeMethodInfo =
103- UnityVersionHandler . Wrap ( ( Il2CppMethodInfo * ) ( IntPtr ) methodField . GetValue ( null ) ) ;
98+ UnityVersionHandler . Wrap ( ( Il2CppMethodInfo * ) ( IntPtr ) methodField . GetValue ( null ) ) ;
10499
105100 // Create a trampoline from the original target method
106101 var trampolinePtr =
@@ -206,19 +201,35 @@ private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo tar
206201 {
207202 // managedParams are the unhollower types used on the managed side
208203 // unmanagedParams are IntPtr references that are used by IL2CPP compiled assembly
209- var paramStartIndex = Original . IsStatic ? 0 : 1 ;
204+ var paramStartIndex = 0 ;
205+
206+ var managedReturnType = AccessTools . GetReturnedType ( Original ) ;
207+ var hasReturnBuffer = managedReturnType . IsSubclassOf ( typeof ( ValueType ) ) &&
208+ PlatformHelper . Is ( Platform . Bits64 ) ;
209+ if ( hasReturnBuffer )
210+ // C compilers seem to return values larger than 64 bits by allocating a return buffer on caller's side and passing it as the first parameter
211+ // TODO: Handle ARM
212+ // TODO: Check if this applies to values other than structs
213+ // TODO: Check if we can use the dummy struct generated by GetFixedSizeStructType() so that mono's marshaller can handle this
214+ paramStartIndex ++ ;
215+
216+ if ( ! Original . IsStatic )
217+ paramStartIndex ++ ;
218+
210219 var managedParams = Original . GetParameters ( ) . Select ( x => x . ParameterType ) . ToArray ( ) ;
211220 var unmanagedParams =
212221 new Type [ managedParams . Length + paramStartIndex +
213- 1 ] ; // +1 for thisptr if needed, +1 for methodInfo at the end
222+ 1 ] ; // +1 for methodInfo at the end
214223
215- if ( ! Original . IsStatic )
224+ if ( hasReturnBuffer )
225+ // With GCC the return buffer seems to be the first param, same is likely with other compilers too
216226 unmanagedParams [ 0 ] = typeof ( IntPtr ) ;
227+ if ( ! Original . IsStatic )
228+ unmanagedParams [ paramStartIndex - 1 ] = typeof ( IntPtr ) ;
217229 unmanagedParams [ ^ 1 ] = typeof ( Il2CppMethodInfo * ) ;
218230 Array . Copy ( managedParams . Select ( ConvertManagedTypeToIL2CPPType ) . ToArray ( ) , 0 ,
219231 unmanagedParams , paramStartIndex , managedParams . Length ) ;
220232
221- var managedReturnType = AccessTools . GetReturnedType ( Original ) ;
222233 var unmanagedReturnType = ConvertManagedTypeToIL2CPPType ( managedReturnType ) ;
223234
224235 var dmd = new DynamicMethodDefinition ( "(il2cpp -> managed) " + Original . Name ,
@@ -234,7 +245,7 @@ private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo tar
234245 var indirectVariables = new LocalBuilder [ managedParams . Length ] ;
235246
236247 if ( ! Original . IsStatic )
237- EmitConvertArgumentToManaged ( il , 0 , Original . DeclaringType , out _ ) ;
248+ EmitConvertArgumentToManaged ( il , paramStartIndex - 1 , Original . DeclaringType , out _ ) ;
238249 for ( var i = 0 ; i < managedParams . Length ; ++ i )
239250 EmitConvertArgumentToManaged ( il , i + paramStartIndex , managedParams [ i ] , out indirectVariables [ i ] ) ;
240251
@@ -270,8 +281,26 @@ private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo tar
270281 // Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return
271282 if ( managedReturnVariable != null )
272283 {
273- il . Emit ( OpCodes . Ldloc , managedReturnVariable ) ;
274- EmitConvertManagedTypeToIL2CPP ( il , managedReturnType ) ;
284+ if ( hasReturnBuffer )
285+ {
286+ uint align = 0 ;
287+ var size = UnhollowerBaseLib . IL2CPP . il2cpp_class_value_size ( Il2CppTypeToClassPointer ( managedReturnType ) ,
288+ ref align ) ;
289+
290+ il . Emit ( OpCodes . Ldarg_0 ) ;
291+ il . Emit ( OpCodes . Ldloc , managedReturnVariable ) ;
292+ il . Emit ( OpCodes . Call , ObjectBaseToPtrMethodInfo ) ;
293+ il . Emit ( OpCodes . Ldc_I4 , size ) ;
294+ il . Emit ( OpCodes . Cpblk ) ;
295+
296+ // Return the same pointer to the return buffer
297+ il . Emit ( OpCodes . Ldarg_0 ) ;
298+ }
299+ else
300+ {
301+ il . Emit ( OpCodes . Ldloc , managedReturnVariable ) ;
302+ EmitConvertManagedTypeToIL2CPP ( il , managedReturnType ) ;
303+ }
275304 }
276305
277306 il . Emit ( OpCodes . Ret ) ;
@@ -322,8 +351,8 @@ private static IntPtr Il2CppTypeToClassPointer(Type type)
322351 {
323352 if ( type == typeof ( void ) )
324353 return Il2CppClassPointerStore < Void > . NativeClassPtr ;
325- return ( IntPtr ) typeof ( Il2CppClassPointerStore < > ) . MakeGenericType ( type ) . GetField ( "NativeClassPtr" )
326- . GetValue ( null ) ;
354+ return ( IntPtr ) typeof ( Il2CppClassPointerStore < > ) . MakeGenericType ( type ) . GetField ( "NativeClassPtr" )
355+ . GetValue ( null ) ;
327356 }
328357
329358 private static void EmitConvertArgumentToManaged ( ILGenerator il ,
@@ -333,13 +362,14 @@ private static void EmitConvertArgumentToManaged(ILGenerator il,
333362 {
334363 variable = null ;
335364
336- if ( managedParamType . IsSubclassOf ( typeof ( ValueType ) ) && ! PlatformHelper . Is ( Platform . Bits64 ) )
365+ if ( managedParamType . IsSubclassOf ( typeof ( ValueType ) ) )
337366 {
338367 // Box struct into object first before conversion
339- // This will likely incur struct copying down the line, but it shouldn't be a massive loss
340368 il . Emit ( OpCodes . Ldc_I8 , Il2CppTypeToClassPointer ( managedParamType ) . ToInt64 ( ) ) ;
341369 il . Emit ( OpCodes . Conv_I ) ;
342- il . Emit ( OpCodes . Ldarga_S , argIndex ) ;
370+ // On x64, struct is always a pointer but it is a non-pointer on x86
371+ // We don't handle byref structs on x86 yet but we're yet to encounter those
372+ il . Emit ( PlatformHelper . Is ( Platform . Bits64 ) ? OpCodes . Ldarg : OpCodes . Ldarga_S , argIndex ) ;
343373 il . Emit ( OpCodes . Call ,
344374 AccessTools . Method ( typeof ( UnhollowerBaseLib . IL2CPP ) ,
345375 nameof ( UnhollowerBaseLib . IL2CPP . il2cpp_value_box ) ) ) ;
0 commit comments