Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
CamelCaseName committed Jul 17, 2023
2 parents 8a6a678 + f7835df commit 444c2f5
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 43 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>1.4.5</VersionPrefix>
<VersionPrefix>1.4.6</VersionPrefix>
<Authors>BepInEx</Authors>
<PackageOutputPath>../bin/NuGet</PackageOutputPath>
<OutputPath Condition="'$(Configuration)' == 'Release'">../bin/$(MSBuildProjectName)</OutputPath>
Expand Down
10 changes: 5 additions & 5 deletions Il2CppInterop.Generator/Extensions/ILGeneratorEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,20 +354,20 @@ public static void EmitPointerToObject(this ILProcessor body, TypeReference orig
}
else
{
var createRealObject = body.Create(OpCodes.Newobj,
new MethodReference(".ctor", imports.Module.Void(), convertedReturnType)
{ Parameters = { new ParameterDefinition(imports.Module.IntPtr()) }, HasThis = true });
var createPoolObject = body.Create(OpCodes.Call,
imports.Module.ImportReference(new GenericInstanceMethod(imports.Il2CppObjectPool_Get.Value)
{ GenericArguments = { convertedReturnType } }));
var endNop = body.Create(OpCodes.Nop);

body.Append(loadPointer);
if (extraDerefForNonValueTypes) body.Emit(OpCodes.Ldind_I);
body.Emit(OpCodes.Dup);
body.Emit(OpCodes.Brtrue_S, createRealObject);
body.Emit(OpCodes.Brtrue_S, createPoolObject);
body.Emit(OpCodes.Pop);
body.Emit(OpCodes.Ldnull);
body.Emit(OpCodes.Br, endNop);

body.Append(createRealObject);
body.Append(createPoolObject);
body.Append(endNop);
}
}
Expand Down
17 changes: 17 additions & 0 deletions Il2CppInterop.Generator/Utils/RuntimeAssemblyReferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext g
public Lazy<MethodReference> IL2CPP_ManagedStringToIl2Cpp { get; private set; }
public Lazy<MethodReference> Il2CppObjectBase_Cast { get; private set; }
public Lazy<MethodReference> Il2CppObjectBase_TryCast { get; private set; }
public Lazy<MethodReference> Il2CppObjectPool_Get { get; private set; }
public Lazy<MethodReference> IL2CPP_ResolveICall { get; private set; }
public Lazy<MethodReference> IL2CPP_il2cpp_gc_wbarrier_set_field { get; private set; }
public Lazy<MethodReference> IL2CPP_FieldWriteWbarrierStub { get; private set; }
Expand Down Expand Up @@ -76,6 +77,7 @@ public RuntimeAssemblyReferences(ModuleDefinition module, RewriteGlobalContext g
: IL2CPP_FieldWriteWbarrierStub.Value;

public TypeReference Il2CppObjectBase { get; private set; }
public TypeReference Il2CppObjectPool { get; private set; }
public TypeReference Il2CppStringArray { get; private set; }
public TypeReference Il2CppArrayBase { get; private set; }
public TypeReference Il2CppStructArray { get; private set; }
Expand Down Expand Up @@ -112,6 +114,8 @@ private void InitTypeRefs()
Il2CppObjectBase =
new TypeReference("Il2CppInterop.Runtime.InteropTypes", "Il2CppObjectBase", Module, assemblyRef);

Il2CppObjectPool = new TypeReference("Il2CppInterop.Runtime.Runtime", "Il2CppObjectPool", Module, assemblyRef);

Il2CppStringArray = new TypeReference("Il2CppInterop.Runtime.InteropTypes.Arrays", "Il2CppStringArray", Module,
assemblyRef);

Expand Down Expand Up @@ -142,6 +146,7 @@ private void InitTypeRefs()
Il2CppException = new TypeReference("Il2CppInterop.Runtime", "Il2CppException", Module, assemblyRef);

allTypes["Il2CppInterop.Runtime.InteropTypes.Il2CppObjectBase"] = Il2CppObjectBase;
allTypes["Il2CppInterop.Runtime.Runtime.Il2CppObjectPool"] = Il2CppObjectPool;
allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStringArray"] = Il2CppStringArray;
allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray<T>"] = Il2CppReferenceArray;
allTypes["Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray<T>"] = Il2CppStructArray;
Expand Down Expand Up @@ -281,6 +286,18 @@ private void InitMethodRefs()
return mr;
});

Il2CppObjectPool_Get = new Lazy<MethodReference>(() =>
{
var mr = new MethodReference("Get", Module.Void(),
ResolveType("Il2CppInterop.Runtime.Runtime.Il2CppObjectPool"));
var gp0 = new GenericParameter("T", mr);
mr.GenericParameters.Add(gp0);
mr.ReturnType = gp0;
mr.HasThis = false;
mr.Parameters.Add(new ParameterDefinition("ptr", ParameterAttributes.None, ResolveType("System.IntPtr")));
return mr;
});

IL2CPP_ResolveICall = new Lazy<MethodReference>(() =>
{
var mr = new MethodReference("ResolveICall", Module.Void(),
Expand Down
2 changes: 1 addition & 1 deletion Il2CppInterop.HarmonySupport/Il2CppDetourMethodPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,7 @@ void EmitCreateIl2CppObject(Type originalType)
il.Emit(OpCodes.Br_S, endLabel);

il.MarkLabel(notNullLabel);
il.Emit(OpCodes.Newobj, AccessTools.DeclaredConstructor(originalType, new[] { typeof(IntPtr) }));
il.Emit(OpCodes.Call, AccessTools.Method(typeof(Il2CppObjectPool), nameof(Il2CppObjectPool.Get)).MakeGenericMethod(originalType));

il.MarkLabel(endLabel);
}
Expand Down
3 changes: 1 addition & 2 deletions Il2CppInterop.Runtime/IL2CPP.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,7 @@ private static T GenerateDelegateForMissingICall<T>(string signature) where T :
if (typeof(T).IsValueType)
return Il2CppObjectBase.UnboxUnsafe<T>(objectPointer);

var il2CppObjectBase = Il2CppObjectBase.CreateUnsafe<T>(objectPointer);
return Unsafe.As<Il2CppObjectBase, T>(ref il2CppObjectBase);
return Il2CppObjectPool.Get<T>(objectPointer);
}

public static string RenderTypeName<T>(bool addRefMarker = false)
Expand Down
2 changes: 1 addition & 1 deletion Il2CppInterop.Runtime/Il2CppInterop.Runtime.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<Platforms>AnyCPU</Platforms>
<ImplicitUsings>false</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Il2CppInterop.HarmonySupport</_Parameter1>
Expand Down
12 changes: 12 additions & 0 deletions Il2CppInterop.Runtime/Injection/Hook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,23 @@ internal abstract class Hook<T> where T : Delegate
public abstract T GetDetour();
public abstract IntPtr FindTargetMethod();

public virtual void TargetMethodNotFound()
{
throw new Exception($"Required target method {TargetMethodName} not found");
}

public void ApplyHook()
{
if (_isApplied) return;

var methodPtr = FindTargetMethod();

if (methodPtr == IntPtr.Zero)
{
TargetMethodNotFound();
return;
}

Logger.Instance.LogTrace("{MethodName} found: 0x{MethodPtr}", TargetMethodName, methodPtr.ToInt64().ToString("X2"));

_detour = GetDetour();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Linq;
using System.Runtime.InteropServices;
using Il2CppInterop.Common;
using Il2CppInterop.Runtime.Runtime;
using Microsoft.Extensions.Logging;

namespace Il2CppInterop.Runtime.Injection.Hooks;

internal class GarbageCollector_RunFinalizer_Patch : Hook<GarbageCollector_RunFinalizer_Patch.MethodDelegate>
{
public override string TargetMethodName => "GarbageCollector::RunFinalizer";
public override MethodDelegate GetDetour() => Hook;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void MethodDelegate(IntPtr obj, IntPtr data);

private void Hook(IntPtr obj, IntPtr data)
{
unsafe
{
var nativeClassStruct = UnityVersionHandler.Wrap((Il2CppClass*)IL2CPP.il2cpp_object_get_class(obj));
if (nativeClassStruct.HasFinalize)
{
Original(obj, data);
}
}
Il2CppObjectPool.Remove(obj);
}

private static readonly MemoryUtils.SignatureDefinition[] s_signatures =
{
new()
{
// Among Us - 2020.3.22 (x86)
pattern = "\x55\x8B\xEC\x51\x56\x8B\x75\x08\xC7\x45\x00\x00\x00\x00\x00",
mask = "xxxxxxxxxx?????",
xref = false
},
new()
{
// Test Game - 2021.3.22 (x64)
pattern = "\x40\x53\x48\x83\xEC\x20\x48\x8B\xD9\x48\xC7\x44\x24\x30\x00\x00\x00\x00\x48\x8B",
mask = "xxxxxxxxxxxxxxxxxxxx",
xref = false,
}
};

public override IntPtr FindTargetMethod()
{
return s_signatures
.Select(s => MemoryUtils.FindSignatureInModule(InjectorHelpers.Il2CppModule, s))
.FirstOrDefault(p => p != 0);
}

public override void TargetMethodNotFound()
{
Il2CppObjectPool.DisableCaching = true;
Logger.Instance.LogWarning("{MethodName} not found, disabling Il2CppObjectPool", TargetMethodName);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Il2CppInterop.Common;
using Il2CppInterop.Common.XrefScans;
using Il2CppInterop.Runtime.Runtime;
using Il2CppInterop.Runtime.Startup;
using Microsoft.Extensions.Logging;

namespace Il2CppInterop.Runtime.Injection.Hooks
Expand Down Expand Up @@ -75,12 +76,26 @@ public override IntPtr FindTargetMethod()
}
else
{
genericMethodGetMethod = getVirtualMethodXrefs.Last();

// U2021.2.0+, there's additional shim that takes 3 parameters
// On U2020.3.41+ there is also a shim, which gets inlined with one added in U2021.2.0+ in release builds
if (UnityVersionHandler.HasShimForGetMethod)
genericMethodGetMethod = XrefScannerLowLevel.JumpTargets(genericMethodGetMethod).Take(2).Last();
{
var shim = getVirtualMethodXrefs.Last();

var shimXrefs = XrefScannerLowLevel.JumpTargets(shim).ToArray();

// If the xref count is 1, it probably means the target is after ret
if (Il2CppInteropRuntime.Instance.UnityVersion.Major == 2020 && shimXrefs.Length == 1)
{
shimXrefs = XrefScannerLowLevel.JumpTargets(shim, true).ToArray();
}

genericMethodGetMethod = shimXrefs.Take(2).Last();
}
else
{
genericMethodGetMethod = getVirtualMethodXrefs.Last();
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions Il2CppInterop.Runtime/Injection/InjectorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ private static void CreateInjectedAssembly()
private static readonly Class_GetFieldDefaultValue_Hook GetFieldDefaultValueHook = new();
private static readonly Class_FromIl2CppType_Hook FromIl2CppTypeHook = new();
private static readonly Class_FromName_Hook FromNameHook = new();
private static readonly GarbageCollector_RunFinalizer_Patch RunFinalizerPatch = new();

internal static void Setup()
{
Expand All @@ -77,6 +78,7 @@ internal static void Setup()
ClassInit ??= FindClassInit();
FromIl2CppTypeHook.ApplyHook();
FromNameHook.ApplyHook();
RunFinalizerPatch.ApplyHook();
}

internal static long CreateClassToken(IntPtr classPointer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ namespace Il2CppInterop.Runtime.InteropTypes.Fields;

public unsafe class Il2CppReferenceField<TRefObj> where TRefObj : Il2CppObjectBase
{
private static bool? isInjectedType;
private readonly IntPtr _fieldPtr;

private readonly Il2CppObjectBase _obj;

internal Il2CppReferenceField(Il2CppObjectBase obj, string fieldName)
Expand All @@ -25,13 +23,7 @@ public TRefObj Value
public TRefObj? Get()
{
var ptr = *GetPointerToData();
if (ptr == IntPtr.Zero) return null;
if (isInjectedType == null)
isInjectedType = RuntimeSpecificsStore.IsInjected(Il2CppClassPointerStore<TRefObj>.NativeClassPtr);

if (isInjectedType.Value && ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ptr) is TRefObj monoObject)
return monoObject;
return (TRefObj)Activator.CreateInstance(typeof(TRefObj), ptr);
return ptr == IntPtr.Zero ? null : Il2CppObjectPool.Get<TRefObj>(ptr);
}

public void Set(TRefObj value)
Expand Down
26 changes: 5 additions & 21 deletions Il2CppInterop.Runtime/InteropTypes/Il2CppObjectBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class Il2CppObjectBase
{
private static readonly MethodInfo _unboxMethod = typeof(Il2CppObjectBase).GetMethod(nameof(Unbox));
internal bool isWrapped;
internal IntPtr pooledPtr;

private uint myGcHandle;

Expand Down Expand Up @@ -86,7 +87,7 @@ public T Unbox<T>() where T : unmanaged
private static readonly MethodInfo _createGCHandle = typeof(Il2CppObjectBase).GetMethod(nameof(CreateGCHandle))!;
private static readonly FieldInfo _isWrapped = typeof(Il2CppObjectBase).GetField(nameof(isWrapped))!;

private static class InitializerStore<T>
internal static class InitializerStore<T>
{
private static Func<IntPtr, T>? _initializer;

Expand Down Expand Up @@ -145,26 +146,6 @@ private static Func<IntPtr, T> Create()
public static Func<IntPtr, T> Initializer => _initializer ??= Create();
}

internal static Il2CppObjectBase CreateUnsafe<T>(IntPtr pointer)
{
var nestedTypeClassPointer = Il2CppClassPointerStore<T>.NativeClassPtr;
if (nestedTypeClassPointer == IntPtr.Zero)
throw new ArgumentException($"{typeof(T)} is not an Il2Cpp reference type");

var ownClass = IL2CPP.il2cpp_object_get_class(pointer);
if (!IL2CPP.il2cpp_class_is_assignable_from(nestedTypeClassPointer, ownClass))
return null;

if (RuntimeSpecificsStore.IsInjected(ownClass))
{
var monoObject = ClassInjectorBase.GetMonoObjectFromIl2CppPointer(pointer);
if (monoObject is T) return (Il2CppObjectBase)monoObject;
}

var il2CppObjectBase = InitializerStore<T>.Initializer(pointer);
return Unsafe.As<T, Il2CppObjectBase>(ref il2CppObjectBase);
}

public T? TryCast<T>() where T : Il2CppObjectBase
{
var nestedTypeClassPointer = Il2CppClassPointerStore<T>.NativeClassPtr;
Expand All @@ -186,5 +167,8 @@ internal static Il2CppObjectBase CreateUnsafe<T>(IntPtr pointer)
~Il2CppObjectBase()
{
IL2CPP.il2cpp_gchandle_free(myGcHandle);

if (pooledPtr == IntPtr.Zero) return;
Il2CppObjectPool.Remove(pooledPtr);
}
}
55 changes: 55 additions & 0 deletions Il2CppInterop.Runtime/Runtime/Il2CppObjectPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using Il2CppInterop.Runtime.InteropTypes;
using Object = Il2CppSystem.Object;

namespace Il2CppInterop.Runtime.Runtime;

public static class Il2CppObjectPool
{
internal static bool DisableCaching { get; set; }

private static readonly ConcurrentDictionary<IntPtr, WeakReference<Il2CppObjectBase>> s_cache = new();

internal static void Remove(IntPtr ptr)
{
s_cache.TryRemove(ptr, out _);
}

public static T Get<T>(IntPtr ptr)
{
if (ptr == IntPtr.Zero) return default;

var ownClass = IL2CPP.il2cpp_object_get_class(ptr);
if (RuntimeSpecificsStore.IsInjected(ownClass))
{
var monoObject = ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ptr);
if (monoObject is T monoObjectT) return monoObjectT;
}

if (DisableCaching) return Il2CppObjectBase.InitializerStore<T>.Initializer(ptr);

if (s_cache.TryGetValue(ptr, out var reference) && reference.TryGetTarget(out var cachedObject))
{
if (cachedObject is T cachedObjectT) return cachedObjectT;
cachedObject.pooledPtr = IntPtr.Zero;
// This leaves the case when you cast to an interface handled as if nothing was cached
}

var newObj = Il2CppObjectBase.InitializerStore<T>.Initializer(ptr);
unsafe
{
var nativeClassStruct = UnityVersionHandler.Wrap((Il2CppClass*)Il2CppClassPointerStore<T>.NativeClassPtr);
if (!nativeClassStruct.HasFinalize)
{
Il2CppSystem.GC.ReRegisterForFinalize(newObj as Object ?? new Object(ptr));
}
}

var il2CppObjectBase = Unsafe.As<T, Il2CppObjectBase>(ref newObj);
s_cache[ptr] = new WeakReference<Il2CppObjectBase>(il2CppObjectBase);
il2CppObjectBase.pooledPtr = ptr;
return newObj;
}
}

0 comments on commit 444c2f5

Please sign in to comment.