4
4
using System . Reflection ;
5
5
using System . Reflection . Emit ;
6
6
using System . Runtime . InteropServices ;
7
- using System . Text ;
8
7
using BepInEx . Logging ;
9
8
using HarmonyLib ;
10
9
using HarmonyLib . Public . Patching ;
11
- using Mono . Cecil . Cil ;
12
10
using MonoMod . Cil ;
13
11
using MonoMod . RuntimeDetour ;
14
12
using MonoMod . Utils ;
15
13
using UnhollowerBaseLib ;
16
14
using UnhollowerBaseLib . Runtime ;
17
15
using UnhollowerBaseLib . Runtime . VersionSpecific . MethodInfo ;
18
- using OpCode = System . Reflection . Emit . OpCode ;
19
- using OpCodes = System . Reflection . Emit . OpCodes ;
20
16
using ValueType = Il2CppSystem . ValueType ;
21
17
using Void = Il2CppSystem . Void ;
22
18
@@ -39,7 +35,6 @@ private static readonly MethodInfo ObjectBaseToPtrMethodInfo
39
35
private static readonly MethodInfo ReportExceptionMethodInfo
40
36
= AccessTools . Method ( typeof ( IL2CPPDetourMethodPatcher ) , nameof ( ReportException ) ) ;
41
37
42
-
43
38
private static readonly ManualLogSource DetourLogger = Logger . CreateLogSource ( "Detour" ) ;
44
39
45
40
// Map each value type to correctly sized store opcode to prevent memory overwrite
@@ -100,7 +95,7 @@ private void Init()
100
95
101
96
// Get the native MethodInfo struct for the target method
102
97
originalNativeMethodInfo =
103
- UnityVersionHandler . Wrap ( ( Il2CppMethodInfo * ) ( IntPtr ) methodField . GetValue ( null ) ) ;
98
+ UnityVersionHandler . Wrap ( ( Il2CppMethodInfo * ) ( IntPtr ) methodField . GetValue ( null ) ) ;
104
99
105
100
// Create a trampoline from the original target method
106
101
var trampolinePtr =
@@ -206,19 +201,35 @@ private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo tar
206
201
{
207
202
// managedParams are the unhollower types used on the managed side
208
203
// 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
+
210
219
var managedParams = Original . GetParameters ( ) . Select ( x => x . ParameterType ) . ToArray ( ) ;
211
220
var unmanagedParams =
212
221
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
214
223
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
216
226
unmanagedParams [ 0 ] = typeof ( IntPtr ) ;
227
+ if ( ! Original . IsStatic )
228
+ unmanagedParams [ paramStartIndex - 1 ] = typeof ( IntPtr ) ;
217
229
unmanagedParams [ ^ 1 ] = typeof ( Il2CppMethodInfo * ) ;
218
230
Array . Copy ( managedParams . Select ( ConvertManagedTypeToIL2CPPType ) . ToArray ( ) , 0 ,
219
231
unmanagedParams , paramStartIndex , managedParams . Length ) ;
220
232
221
- var managedReturnType = AccessTools . GetReturnedType ( Original ) ;
222
233
var unmanagedReturnType = ConvertManagedTypeToIL2CPPType ( managedReturnType ) ;
223
234
224
235
var dmd = new DynamicMethodDefinition ( "(il2cpp -> managed) " + Original . Name ,
@@ -234,7 +245,7 @@ private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo tar
234
245
var indirectVariables = new LocalBuilder [ managedParams . Length ] ;
235
246
236
247
if ( ! Original . IsStatic )
237
- EmitConvertArgumentToManaged ( il , 0 , Original . DeclaringType , out _ ) ;
248
+ EmitConvertArgumentToManaged ( il , paramStartIndex - 1 , Original . DeclaringType , out _ ) ;
238
249
for ( var i = 0 ; i < managedParams . Length ; ++ i )
239
250
EmitConvertArgumentToManaged ( il , i + paramStartIndex , managedParams [ i ] , out indirectVariables [ i ] ) ;
240
251
@@ -270,8 +281,26 @@ private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo tar
270
281
// Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return
271
282
if ( managedReturnVariable != null )
272
283
{
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
+ }
275
304
}
276
305
277
306
il . Emit ( OpCodes . Ret ) ;
@@ -322,8 +351,8 @@ private static IntPtr Il2CppTypeToClassPointer(Type type)
322
351
{
323
352
if ( type == typeof ( void ) )
324
353
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 ) ;
327
356
}
328
357
329
358
private static void EmitConvertArgumentToManaged ( ILGenerator il ,
@@ -333,13 +362,14 @@ private static void EmitConvertArgumentToManaged(ILGenerator il,
333
362
{
334
363
variable = null ;
335
364
336
- if ( managedParamType . IsSubclassOf ( typeof ( ValueType ) ) && ! PlatformHelper . Is ( Platform . Bits64 ) )
365
+ if ( managedParamType . IsSubclassOf ( typeof ( ValueType ) ) )
337
366
{
338
367
// 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
340
368
il . Emit ( OpCodes . Ldc_I8 , Il2CppTypeToClassPointer ( managedParamType ) . ToInt64 ( ) ) ;
341
369
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 ) ;
343
373
il . Emit ( OpCodes . Call ,
344
374
AccessTools . Method ( typeof ( UnhollowerBaseLib . IL2CPP ) ,
345
375
nameof ( UnhollowerBaseLib . IL2CPP . il2cpp_value_box ) ) ) ;
0 commit comments