From 7c0ccbf93ecf6ba2648413520fabeea8db5d46ff Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Sat, 6 May 2023 19:13:03 +0300 Subject: [PATCH 01/13] Preliminary support of assembly injection --- .../ClassInjectionAssemblyTargetAttribute.cs | 42 +++++++++- Il2CppInterop.Runtime/IL2CPP.cs | 19 +++-- .../Injection/ClassInjector.cs | 8 +- .../Injection/EnumInjector.cs | 2 +- .../Hooks/AppDomain_GetAssemblies_Hook.cs | 76 +++++++++++++++++++ .../Hooks/Assembly_GetLoadedAssembly_Hook.cs | 54 +++++++++++++ .../Injection/Hooks/Assembly_Load_Hook.cs | 45 +++++++++++ .../Injection/InjectorHelpers.cs | 57 +++++++++++--- 8 files changed, 278 insertions(+), 25 deletions(-) create mode 100644 Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs create mode 100644 Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs create mode 100644 Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs diff --git a/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs b/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs index 4916018c..b2cef54a 100644 --- a/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs +++ b/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs @@ -1,23 +1,41 @@ using System; using System.Collections.Generic; +using Il2CppInterop.Runtime.Injection; namespace Il2CppInterop.Runtime.Attributes; [AttributeUsage(AttributeTargets.Class)] public class ClassInjectionAssemblyTargetAttribute : Attribute { + private readonly AssemblyKind assemblyKind; private readonly string[] assemblies; public ClassInjectionAssemblyTargetAttribute(string assembly) { - if (string.IsNullOrWhiteSpace(assembly)) assemblies = new string[0]; + if (string.IsNullOrWhiteSpace(assembly)) assemblies = Array.Empty(); else assemblies = new[] { assembly }; + assemblyKind = AssemblyKind.IL2CPP; } public ClassInjectionAssemblyTargetAttribute(string[] assemblies) { - if (assemblies is null) this.assemblies = new string[0]; + if (assemblies is null) this.assemblies = Array.Empty(); else this.assemblies = assemblies; + assemblyKind = AssemblyKind.IL2CPP; + } + + public ClassInjectionAssemblyTargetAttribute(string assembly, AssemblyKind assemblyKind) + { + if (string.IsNullOrWhiteSpace(assembly)) assemblies = Array.Empty(); + else assemblies = new[] { assembly }; + this.assemblyKind = assemblyKind; + } + + public ClassInjectionAssemblyTargetAttribute(string[] assemblies, AssemblyKind assemblyKind) + { + if (assemblies is null) this.assemblies = Array.Empty(); + else this.assemblies = assemblies; + this.assemblyKind = assemblyKind; } internal IntPtr[] GetImagePointers() @@ -25,10 +43,26 @@ internal IntPtr[] GetImagePointers() var result = new List(); foreach (var assembly in assemblies) { - var intPtr = IL2CPP.GetIl2CppImage(assembly); - if (intPtr != IntPtr.Zero) result.Add(intPtr); + IntPtr intPtr; + switch (assemblyKind) + { + case AssemblyKind.IL2CPP: + intPtr = IL2CPP.GetIl2CppImage(assembly); + if (intPtr != IntPtr.Zero) result.Add(intPtr); + break; + case AssemblyKind.INJECTED: + intPtr = InjectorHelpers.GetOrCreateInjectedImage(assembly); + if (intPtr != IntPtr.Zero) result.Add(intPtr); + break; + } } return result.ToArray(); } } + +public enum AssemblyKind +{ + IL2CPP, + INJECTED +} diff --git a/Il2CppInterop.Runtime/IL2CPP.cs b/Il2CppInterop.Runtime/IL2CPP.cs index 7604d9c0..5cfb0c97 100644 --- a/Il2CppInterop.Runtime/IL2CPP.cs +++ b/Il2CppInterop.Runtime/IL2CPP.cs @@ -9,6 +9,7 @@ using System.Text.RegularExpressions; using Il2CppInterop.Common; using Il2CppInterop.Common.Attributes; +using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppInterop.Runtime.Runtime; @@ -18,7 +19,7 @@ namespace Il2CppInterop.Runtime; public static unsafe class IL2CPP { - private static readonly Dictionary ourImagesMap = new(); + private static readonly Dictionary il2CppImagesMap = new(); static IL2CPP() { @@ -35,27 +36,31 @@ static IL2CPP() { var image = il2cpp_assembly_get_image(assemblies[i]); var name = Marshal.PtrToStringAnsi(il2cpp_image_get_name(image)); - ourImagesMap[name] = image; + il2CppImagesMap[name] = image; } } internal static IntPtr GetIl2CppImage(string name) { - if (ourImagesMap.ContainsKey(name)) return ourImagesMap[name]; + if (il2CppImagesMap.ContainsKey(name)) return il2CppImagesMap[name]; + if (InjectorHelpers.InjectedImages.ContainsKey(name)) return InjectorHelpers.InjectedImages[name]; return IntPtr.Zero; } internal static IntPtr[] GetIl2CppImages() { - return ourImagesMap.Values.ToArray(); + return il2CppImagesMap.Values.ToArray(); } public static IntPtr GetIl2CppClass(string assemblyName, string namespaze, string className) { - if (!ourImagesMap.TryGetValue(assemblyName, out var image)) + if (!il2CppImagesMap.TryGetValue(assemblyName, out var image)) { - Logger.Instance.LogError("Assembly {AssemblyName} is not registered in il2cpp", assemblyName); - return IntPtr.Zero; + if (!InjectorHelpers.InjectedImages.TryGetValue(assemblyName, out image)) + { + Logger.Instance.LogError("Assembly {AssemblyName} is not registered in il2cpp", assemblyName); + return IntPtr.Zero; + } } var clazz = il2cpp_class_from_name(image, namespaze, className); diff --git a/Il2CppInterop.Runtime/Injection/ClassInjector.cs b/Il2CppInterop.Runtime/Injection/ClassInjector.cs index 6db08110..2700c53d 100644 --- a/Il2CppInterop.Runtime/Injection/ClassInjector.cs +++ b/Il2CppInterop.Runtime/Injection/ClassInjector.cs @@ -223,7 +223,13 @@ public static void RegisterTypeInIl2Cpp(Type type, RegisterTypeOptions options) var interfaceFunctionCount = interfaces.Sum(i => i.MethodCount); var classPointer = UnityVersionHandler.NewClass(baseClassPointer.VtableCount + interfaceFunctionCount); - classPointer.Image = InjectorHelpers.InjectedImage.ImagePointer; + classPointer.Image = InjectorHelpers.DefaultInjectedImage.ImagePointer; + + if (InjectorHelpers.TryGetInjectedImageForAssembly(type.Assembly, out var imagePtr)) + { + classPointer.Image = (Il2CppImage*)imagePtr; + } + classPointer.Parent = baseClassPointer.ClassPointer; classPointer.ElementClass = classPointer.Class = classPointer.CastClass = classPointer.ClassPointer; classPointer.NativeSize = -1; diff --git a/Il2CppInterop.Runtime/Injection/EnumInjector.cs b/Il2CppInterop.Runtime/Injection/EnumInjector.cs index 71a58ca9..e41fa983 100644 --- a/Il2CppInterop.Runtime/Injection/EnumInjector.cs +++ b/Il2CppInterop.Runtime/Injection/EnumInjector.cs @@ -206,7 +206,7 @@ public static void RegisterEnumInIl2Cpp(Type type, bool logSuccess = true) UnityVersionHandler.Wrap( (Il2CppClass*)Il2CppClassPointerStore.GetNativeClassPointer(Enum.GetUnderlyingType(type))); - il2cppEnum.Image = InjectorHelpers.InjectedImage.ImagePointer; + il2cppEnum.Image = InjectorHelpers.DefaultInjectedImage.ImagePointer; il2cppEnum.Class = il2cppEnum.CastClass = il2cppEnum.ElementClass = elementClass.ClassPointer; il2cppEnum.Parent = baseEnum.ClassPointer; il2cppEnum.ActualSize = il2cppEnum.InstanceSize = diff --git a/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs new file mode 100644 index 00000000..d17c9793 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Common.Extensions; +using Il2CppInterop.Common.XrefScans; +using Il2CppInterop.Runtime.InteropTypes.Arrays; +using Il2CppInterop.Runtime.Runtime; +using Il2CppInterop.Runtime.Startup; +using Microsoft.Extensions.Logging; +using AppDomain = Il2CppSystem.AppDomain; +using Assembly = Il2CppSystem.Reflection.Assembly; + +namespace Il2CppInterop.Runtime.Injection.Hooks +{ + internal unsafe class AppDomain_GetAssemblies_Hook : Hook + { + public override MethodDelegate GetDetour() => Hook; + public override string TargetMethodName => "AppDomain::GetAssemblies"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate IntPtr MethodDelegate(IntPtr thisPtr, byte refOnly); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate IntPtr GetAssemblyObject(IntPtr thisPtr); + + private GetAssemblyObject getAssemblyObjectDelegate; + + private IntPtr Hook(IntPtr thisPtr, byte refOnly) + { + IntPtr assemblyArrayPtr = Original(thisPtr, refOnly); + Il2CppReferenceArray assemblyArray = new Il2CppReferenceArray(assemblyArrayPtr); + + if (InjectorHelpers.InjectedImages.Count > 0) + { + var newSize = assemblyArray.Length + InjectorHelpers.InjectedImages.Count; + Il2CppReferenceArray newAssemblyArray = new Il2CppReferenceArray(newSize); + int i; + + for (i = 0; i < assemblyArray.Length; i++) + newAssemblyArray[i] = assemblyArray[i]; + + i = assemblyArray.Length; + foreach (IntPtr imagePtr in InjectorHelpers.InjectedImages.Values) + { + var image = UnityVersionHandler.Wrap((Il2CppImage*)imagePtr); + newAssemblyArray[i] = new Assembly(getAssemblyObjectDelegate((IntPtr)image.Assembly)); + i++; + } + + return newAssemblyArray.Pointer; + } + + return assemblyArrayPtr; + } + + public override IntPtr FindTargetMethod() + { + var appDomain = InjectorHelpers.Il2CppMscorlib.GetTypesSafe().SingleOrDefault((x) => x.Name is "AppDomain"); + if (appDomain == null) + throw new Exception($"Unity {Il2CppInteropRuntime.Instance.UnityVersion} is not supported at the moment: System.AppDomain isn't present in Il2Cppmscorlib.dll for unity version, unable to fetch icall"); + + var GetAssembliesThunk = InjectorHelpers.GetIl2CppMethodPointer(appDomain.GetMethod("GetAssemblies", unchecked((BindingFlags)0xffffffff), new[]{typeof(bool)})); + Logger.Instance.LogTrace("Il2CppSystem.AppDomain::thunk_GetAssemblies: 0x{GetAssembliesThunk}", GetAssembliesThunk.ToInt64().ToString("X2")); + + var myMethod = XrefScannerLowLevel.JumpTargets(GetAssembliesThunk).Single(); + var getAssemblyObject = XrefScannerLowLevel.JumpTargets(myMethod).SkipLast(1).Last(); + Logger.Instance.LogTrace("Reflection::GetAssemblyObject: 0x{getAssemblyObject}", getAssemblyObject.ToInt64().ToString("X2")); + + getAssemblyObjectDelegate = Marshal.GetDelegateForFunctionPointer(getAssemblyObject); + + return myMethod; + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs new file mode 100644 index 00000000..4fb321db --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs @@ -0,0 +1,54 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Common.Extensions; +using Il2CppInterop.Common.XrefScans; +using Il2CppInterop.Runtime.Runtime; +using Il2CppInterop.Runtime.Startup; +using Il2CppSystem.Reflection; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime.Injection.Hooks +{ + internal unsafe class Assembly_GetLoadedAssembly_Hook : Hook + { + public override MethodDelegate GetDetour() => Hook; + public override string TargetMethodName => "Assembly::GetLoadedAssembly"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate Il2CppAssembly* MethodDelegate(IntPtr name); + + private Il2CppAssembly* Hook(IntPtr name) + { + Il2CppAssembly* assembly = Original(name); + + if (assembly == null) + { + var assemblyName = Marshal.PtrToStringAnsi(name); + if (InjectorHelpers.InjectedImages.TryGetValue(assemblyName, out var ptr)) + { + var image = UnityVersionHandler.Wrap((Il2CppImage*)ptr); + assembly = image.Assembly; + } + } + + return assembly; + } + + public override IntPtr FindTargetMethod() + { + var assembly = InjectorHelpers.Il2CppMscorlib.GetTypesSafe().SingleOrDefault((x) => x.Name is "Assembly"); + if (assembly == null) + throw new Exception($"Unity {Il2CppInteropRuntime.Instance.UnityVersion} is not supported at the moment: System.Reflection.Assembly isn't present in Il2Cppmscorlib.dll for unity version, unable to fetch icall"); + + var loadWithPartialNameThunk = InjectorHelpers.GetIl2CppMethodPointer(assembly.GetMethod(nameof(Assembly.load_with_partial_name))); + Logger.Instance.LogTrace("Il2CppSystem.Reflection.Assembly::thunk_load_with_partial_name: 0x{loadWithPartialNameThunk}", loadWithPartialNameThunk.ToInt64().ToString("X2")); + + var loadWithPartialName = XrefScannerLowLevel.JumpTargets(loadWithPartialNameThunk).Last(); + Logger.Instance.LogTrace("Il2CppSystem.Reflection.Assembly::load_with_partial_name: 0x{loadWithPartialName}", loadWithPartialName.ToInt64().ToString("X2")); + + return XrefScannerLowLevel.JumpTargets(loadWithPartialName).ElementAt(1); + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs new file mode 100644 index 00000000..3d413e12 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs @@ -0,0 +1,45 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using Il2CppInterop.Common; +using Il2CppInterop.Common.XrefScans; +using Il2CppInterop.Runtime.Runtime; +using Microsoft.Extensions.Logging; + +namespace Il2CppInterop.Runtime.Injection.Hooks +{ + internal unsafe class Assembly_Load_Hook : Hook + { + public override MethodDelegate GetDetour() => Hook; + public override string TargetMethodName => "Assembly::Load"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate Il2CppAssembly* MethodDelegate(IntPtr name); + + private Il2CppAssembly* Hook(IntPtr name) + { + Il2CppAssembly* assembly = Original(name); + + if (assembly == null) + { + var assemblyName = Marshal.PtrToStringAnsi(name); + if (InjectorHelpers.InjectedImages.TryGetValue(assemblyName, out var ptr)) + { + var image = UnityVersionHandler.Wrap((Il2CppImage*)ptr); + assembly = image.Assembly; + } + } + + return assembly; + } + + public override IntPtr FindTargetMethod() + { + var domainAssemblyOpenPtr = InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_domain_assembly_open)); + Logger.Instance.LogTrace("il2cpp_domain_assembly_open: 0x{domainAssemblyOpenPtr}", domainAssemblyOpenPtr.ToInt64().ToString("X2")); + + return XrefScannerLowLevel.JumpTargets(domainAssemblyOpenPtr).Single(); + } + } +} + diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs index b1729e42..d5f57522 100644 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs @@ -25,8 +25,10 @@ namespace Il2CppInterop.Runtime.Injection internal static unsafe class InjectorHelpers { internal static Assembly Il2CppMscorlib = typeof(Il2CppSystem.Type).Assembly; - internal static INativeAssemblyStruct InjectedAssembly; - internal static INativeImageStruct InjectedImage; + + internal static Dictionary InjectedImages = new Dictionary(); + internal static INativeAssemblyStruct DefaultInjectedAssembly; + internal static INativeImageStruct DefaultInjectedImage; internal static ProcessModule Il2CppModule = Process.GetCurrentProcess() .Modules.OfType() .Single((x) => x.ModuleName is "GameAssembly.dll" or "GameAssembly.so" or "UserAssembly.dll"); @@ -48,18 +50,42 @@ internal static unsafe class InjectorHelpers [typeof(double)] = OpCodes.Stind_R8 }; - private static void CreateInjectedAssembly() + private static void CreateDefaultInjectedAssembly() + { + DefaultInjectedImage = CreateInjectedImage("InjectedMonoTypes"); + DefaultInjectedAssembly = UnityVersionHandler.Wrap(DefaultInjectedImage.Assembly); + } + + private static INativeImageStruct CreateInjectedImage(string name) { - InjectedAssembly = UnityVersionHandler.NewAssembly(); - InjectedImage = UnityVersionHandler.NewImage(); + Logger.Instance.LogTrace($"Creating injected assembly {name}"); + var assembly = UnityVersionHandler.NewAssembly(); + var image = UnityVersionHandler.NewImage(); - InjectedAssembly.Name.Name = Marshal.StringToHGlobalAnsi("InjectedMonoTypes"); - InjectedImage.Assembly = InjectedAssembly.AssemblyPointer; - InjectedImage.Dynamic = 1; - InjectedImage.Name = InjectedAssembly.Name.Name; - if (InjectedImage.HasNameNoExt) - InjectedImage.NameNoExt = InjectedAssembly.Name.Name; + assembly.Name.Name = Marshal.StringToHGlobalAnsi(name); + + image.Assembly = assembly.AssemblyPointer; + image.Dynamic = 1; + image.Name = assembly.Name.Name; + if (image.HasNameNoExt) + image.NameNoExt = assembly.Name.Name; + assembly.Image = image.ImagePointer; + InjectedImages.Add(name, image.Pointer); + return image; + } + + internal static IntPtr GetOrCreateInjectedImage(string name) + { + if (InjectedImages.TryGetValue(name, out var ptr)) + return ptr; + + return CreateInjectedImage(name).Pointer; + } + + internal static bool TryGetInjectedImageForAssembly(Assembly assembly, out IntPtr ptr) + { + return InjectedImages.TryGetValue(assembly.GetName().Name, out ptr); } private static readonly GenericMethod_GetMethod_Hook GenericMethodGetMethodHook = new(); @@ -69,9 +95,13 @@ private static void CreateInjectedAssembly() private static readonly Class_FromName_Hook FromNameHook = new(); private static readonly GarbageCollector_RunFinalizer_Patch RunFinalizerPatch = new(); + private static readonly Assembly_Load_Hook assemblyLoadHook = new(); + private static readonly Assembly_GetLoadedAssembly_Hook AssemblyGetLoadedAssemblyHook = new(); + private static readonly AppDomain_GetAssemblies_Hook AppDomainGetAssembliesHook = new(); + internal static void Setup() { - if (InjectedAssembly == null) CreateInjectedAssembly(); + if (DefaultInjectedAssembly == null) CreateDefaultInjectedAssembly(); GenericMethodGetMethodHook.ApplyHook(); GetTypeInfoFromTypeDefinitionIndexHook.ApplyHook(); GetFieldDefaultValueHook.ApplyHook(); @@ -79,6 +109,9 @@ internal static void Setup() FromIl2CppTypeHook.ApplyHook(); FromNameHook.ApplyHook(); RunFinalizerPatch.ApplyHook(); + assemblyLoadHook.ApplyHook(); + AssemblyGetLoadedAssemblyHook.ApplyHook(); + AppDomainGetAssembliesHook.ApplyHook(); } internal static long CreateClassToken(IntPtr classPointer) From f4803f7d86f467dd03dfecdef38e4445e1837274 Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Thu, 25 May 2023 12:48:09 +0300 Subject: [PATCH 02/13] Inject assemblies early --- .../API_il2cpp_domain_get_assemblies_hook.cs | 63 +++++++++++++++++++ .../Hooks/AppDomain_GetAssemblies_Hook.cs | 1 + .../Hooks/Assembly_GetLoadedAssembly_Hook.cs | 2 +- .../Injection/Hooks/Class_FromName_Hook.cs | 1 + ...GetTypeInfoFromTypeDefinitionIndex_Hook.cs | 2 + .../Injection/InjectorHelpers.cs | 62 +++++++++++++++--- .../Startup/Il2CppInteropRuntime.cs | 1 + 7 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs diff --git a/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs new file mode 100644 index 00000000..4807518d --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs @@ -0,0 +1,63 @@ +using System; +using System.Runtime.InteropServices; +using Il2CppInterop.Runtime.Runtime; + +namespace Il2CppInterop.Runtime.Injection.Hooks +{ + internal unsafe class API_il2cpp_domain_get_assemblies_hook : Hook + { + public override MethodDelegate GetDetour() => Hook; + public override string TargetMethodName => "il2cpp_domain_get_assemblies"; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate IntPtr MethodDelegate(IntPtr domain, long* size); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate IntPtr GetAssemblyObject(IntPtr thisPtr); + + private GetAssemblyObject getAssemblyObjectDelegate; + + private IntPtr currentDataPtr = IntPtr.Zero; + + private IntPtr Hook(IntPtr domain, long* size) + { + IntPtr assemblyArrayPtr = Original(domain, size); + + if (InjectorHelpers.InjectedImages.Count > 0) + { + Il2CppAssembly** oldArray = (Il2CppAssembly**)assemblyArrayPtr; + int origSize = (int)*size; + + int newSize = origSize + InjectorHelpers.InjectedImages.Count; + if (currentDataPtr != IntPtr.Zero) + Marshal.FreeHGlobal(currentDataPtr); + + currentDataPtr = Marshal.AllocHGlobal(newSize * sizeof(Il2CppSystem.IntPtr)); + Il2CppAssembly** newArray = (Il2CppAssembly**)currentDataPtr; + + int i; + + for (i = 0; i < origSize; i++) + newArray[i] = oldArray[i]; + + i = origSize; + foreach (IntPtr imagePtr in InjectorHelpers.InjectedImages.Values) + { + var image = UnityVersionHandler.Wrap((Il2CppImage*)imagePtr); + newArray[i] = image.Assembly; + i++; + } + + *size = newSize; + return currentDataPtr; + } + + return assemblyArrayPtr; + } + + public override IntPtr FindTargetMethod() + { + return InjectorHelpers.GetIl2CppExport(nameof(IL2CPP.il2cpp_domain_get_assemblies)); + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs index d17c9793..b9bfb8e7 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs @@ -29,6 +29,7 @@ internal unsafe class AppDomain_GetAssemblies_Hook : Hook assemblyArray = new Il2CppReferenceArray(assemblyArrayPtr); diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs index 4fb321db..ccc2cfbe 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs @@ -21,11 +21,11 @@ internal unsafe class Assembly_GetLoadedAssembly_Hook : Hook InjectedImages = new Dictionary(); @@ -52,7 +50,7 @@ internal static unsafe class InjectorHelpers private static void CreateDefaultInjectedAssembly() { - DefaultInjectedImage = CreateInjectedImage("InjectedMonoTypes"); + DefaultInjectedImage = CreateInjectedImage(InjectedMonoTypesAssemblyName); DefaultInjectedAssembly = UnityVersionHandler.Wrap(DefaultInjectedImage.Assembly); } @@ -69,7 +67,13 @@ private static INativeImageStruct CreateInjectedImage(string name) image.Dynamic = 1; image.Name = assembly.Name.Name; if (image.HasNameNoExt) - image.NameNoExt = assembly.Name.Name; + { + if (name.EndsWith(".dll")) + image.NameNoExt = Marshal.StringToHGlobalAnsi(name.Replace(".dll", "")); + else + image.NameNoExt = assembly.Name.Name; + } + assembly.Image = image.ImagePointer; InjectedImages.Add(name, image.Pointer); return image; @@ -96,12 +100,13 @@ internal static bool TryGetInjectedImageForAssembly(Assembly assembly, out IntPt private static readonly GarbageCollector_RunFinalizer_Patch RunFinalizerPatch = new(); private static readonly Assembly_Load_Hook assemblyLoadHook = new(); + private static readonly API_il2cpp_domain_get_assemblies_hook api_get_assemblies = new(); + private static readonly Assembly_GetLoadedAssembly_Hook AssemblyGetLoadedAssemblyHook = new(); private static readonly AppDomain_GetAssemblies_Hook AppDomainGetAssembliesHook = new(); internal static void Setup() { - if (DefaultInjectedAssembly == null) CreateDefaultInjectedAssembly(); GenericMethodGetMethodHook.ApplyHook(); GetTypeInfoFromTypeDefinitionIndexHook.ApplyHook(); GetFieldDefaultValueHook.ApplyHook(); @@ -114,7 +119,46 @@ internal static void Setup() AppDomainGetAssembliesHook.ApplyHook(); } - internal static long CreateClassToken(IntPtr classPointer) + // Setup before unity loads assembly list + internal static void EarlySetup() + { + var executablePath = Environment.GetEnvironmentVariable("DOORSTOP_PROCESS_PATH"); + var processPath = Path.GetFileNameWithoutExtension(executablePath); + var assemblyNamesFile = $"{processPath}_Data/ScriptingAssemblies.json"; + var assemblyNamesFileBackup = $"{processPath}_Data/ScriptingAssemblies_BACKUP.json"; + if (!File.Exists(assemblyNamesFileBackup)) + { + File.Copy(assemblyNamesFile, assemblyNamesFileBackup); + } + + var jsonNode = JsonNode.Parse(File.ReadAllText(assemblyNamesFileBackup)); + var names = jsonNode["names"].AsArray(); + var types = jsonNode["types"].AsArray(); + + names.Add(InjectedMonoTypesAssemblyName); + types.Add(16); + if (DefaultInjectedAssembly == null) CreateDefaultInjectedAssembly(); + var allFiles = Directory.EnumerateFiles("BepInEx/plugins/", "*", SearchOption.AllDirectories); + foreach (var file in allFiles) + { + var extension = Path.GetExtension(file); + if (extension.Equals(".dll")) + { + var assemblyName = Path.GetFileName(file); + CreateInjectedImage(assemblyName); + names.Add(assemblyName); + types.Add(16); + } + } + + var newJson = jsonNode.ToJsonString(); + File.WriteAllText(assemblyNamesFile, newJson); + + assemblyLoadHook.ApplyHook(); + api_get_assemblies.ApplyHook(); + } + + internal static long CreateTypeToken(IntPtr classPointer) { long newToken = Interlocked.Decrement(ref s_LastInjectedToken); s_InjectedClasses[newToken] = classPointer; diff --git a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs index 3147986b..1492a71a 100644 --- a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs +++ b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs @@ -40,6 +40,7 @@ public static Il2CppInteropRuntime Create(RuntimeConfiguration configuration) public override void Start() { UnityVersionHandler.RecalculateHandlers(); + InjectorHelpers.EarlySetup(); base.Start(); } } From ad055db36ae45cfb8e11500da546c271b9d97724 Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Mon, 29 May 2023 17:50:44 +0300 Subject: [PATCH 03/13] Tidy up code --- .../Injection/AssemblyListFile.cs | 42 +++++++++++++++++++ .../Hooks/AppDomain_GetAssemblies_Hook.cs | 2 - .../Injection/InjectorHelpers.cs | 40 +++++++----------- 3 files changed, 56 insertions(+), 28 deletions(-) create mode 100644 Il2CppInterop.Runtime/Injection/AssemblyListFile.cs diff --git a/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs b/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs new file mode 100644 index 00000000..633aca9f --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using System.Text.Json.Nodes; + +namespace Il2CppInterop.Runtime.Injection +{ + public class AssemblyListFile : IDisposable + { + private readonly string assemblyNamesFile; + private readonly JsonNode node; + private readonly JsonArray names; + private readonly JsonArray types; + + public AssemblyListFile() + { + var executablePath = Environment.GetEnvironmentVariable("DOORSTOP_PROCESS_PATH"); + var processPath = Path.GetFileNameWithoutExtension(executablePath); + assemblyNamesFile = $"{processPath}_Data/ScriptingAssemblies.json"; + var assemblyNamesFileBackup = $"{processPath}_Data/ScriptingAssemblies_BACKUP.json"; + if (!File.Exists(assemblyNamesFileBackup)) + { + File.Copy(assemblyNamesFile, assemblyNamesFileBackup); + } + + node = JsonNode.Parse(File.ReadAllText(assemblyNamesFileBackup)); + names = node["names"].AsArray(); + types = node["types"].AsArray(); + } + + public void AddAssembly(string name) + { + names.Add(name); + types.Add(16); + } + + public void Dispose() + { + var newJson = node.ToJsonString(); + File.WriteAllText(assemblyNamesFile, newJson); + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs index b9bfb8e7..411972df 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs @@ -9,7 +9,6 @@ using Il2CppInterop.Runtime.Runtime; using Il2CppInterop.Runtime.Startup; using Microsoft.Extensions.Logging; -using AppDomain = Il2CppSystem.AppDomain; using Assembly = Il2CppSystem.Reflection.Assembly; namespace Il2CppInterop.Runtime.Injection.Hooks @@ -29,7 +28,6 @@ internal unsafe class AppDomain_GetAssemblies_Hook : Hook assemblyArray = new Il2CppReferenceArray(assemblyArrayPtr); diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs index bb592360..982d25e2 100644 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs @@ -122,38 +122,26 @@ internal static void Setup() // Setup before unity loads assembly list internal static void EarlySetup() { - var executablePath = Environment.GetEnvironmentVariable("DOORSTOP_PROCESS_PATH"); - var processPath = Path.GetFileNameWithoutExtension(executablePath); - var assemblyNamesFile = $"{processPath}_Data/ScriptingAssemblies.json"; - var assemblyNamesFileBackup = $"{processPath}_Data/ScriptingAssemblies_BACKUP.json"; - if (!File.Exists(assemblyNamesFileBackup)) + using (AssemblyListFile assemblyList = new()) { - File.Copy(assemblyNamesFile, assemblyNamesFileBackup); - } - - var jsonNode = JsonNode.Parse(File.ReadAllText(assemblyNamesFileBackup)); - var names = jsonNode["names"].AsArray(); - var types = jsonNode["types"].AsArray(); + CreateDefaultInjectedAssembly(); + assemblyList.AddAssembly(InjectedMonoTypesAssemblyName); + var coreFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var pluginsFolder = Path.Combine(coreFolder, "../plugins/"); - names.Add(InjectedMonoTypesAssemblyName); - types.Add(16); - if (DefaultInjectedAssembly == null) CreateDefaultInjectedAssembly(); - var allFiles = Directory.EnumerateFiles("BepInEx/plugins/", "*", SearchOption.AllDirectories); - foreach (var file in allFiles) - { - var extension = Path.GetExtension(file); - if (extension.Equals(".dll")) + var allFiles = Directory.EnumerateFiles(pluginsFolder, "*", SearchOption.AllDirectories); + foreach (var file in allFiles) { - var assemblyName = Path.GetFileName(file); - CreateInjectedImage(assemblyName); - names.Add(assemblyName); - types.Add(16); + var extension = Path.GetExtension(file); + if (extension.Equals(".dll")) + { + var assemblyName = Path.GetFileName(file); + CreateInjectedImage(assemblyName); + assemblyList.AddAssembly(assemblyName); + } } } - var newJson = jsonNode.ToJsonString(); - File.WriteAllText(assemblyNamesFile, newJson); - assemblyLoadHook.ApplyHook(); api_get_assemblies.ApplyHook(); } From c503551d62cf9eabb3778910c0243e5c234a5bae Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Mon, 29 May 2023 19:23:49 +0300 Subject: [PATCH 04/13] Fix remaining issues --- .../Injection/EnumInjector.cs | 6 +++ .../Hooks/Assembly_GetLoadedAssembly_Hook.cs | 2 +- .../Injection/Hooks/Assembly_Load_Hook.cs | 2 +- .../Injection/InjectorHelpers.cs | 42 ++++++++++--------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/Il2CppInterop.Runtime/Injection/EnumInjector.cs b/Il2CppInterop.Runtime/Injection/EnumInjector.cs index e41fa983..d513ef30 100644 --- a/Il2CppInterop.Runtime/Injection/EnumInjector.cs +++ b/Il2CppInterop.Runtime/Injection/EnumInjector.cs @@ -207,6 +207,12 @@ public static void RegisterEnumInIl2Cpp(Type type, bool logSuccess = true) (Il2CppClass*)Il2CppClassPointerStore.GetNativeClassPointer(Enum.GetUnderlyingType(type))); il2cppEnum.Image = InjectorHelpers.DefaultInjectedImage.ImagePointer; + + if (InjectorHelpers.TryGetInjectedImageForAssembly(type.Assembly, out var imagePtr)) + { + il2cppEnum.Image = (Il2CppImage*)imagePtr; + } + il2cppEnum.Class = il2cppEnum.CastClass = il2cppEnum.ElementClass = elementClass.ClassPointer; il2cppEnum.Parent = baseEnum.ClassPointer; il2cppEnum.ActualSize = il2cppEnum.InstanceSize = diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs index ccc2cfbe..246a9255 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_GetLoadedAssembly_Hook.cs @@ -26,7 +26,7 @@ internal unsafe class Assembly_GetLoadedAssembly_Hook : Hook InjectedImages = new Dictionary(); - internal static INativeAssemblyStruct DefaultInjectedAssembly; internal static INativeImageStruct DefaultInjectedImage; internal static ProcessModule Il2CppModule = Process.GetCurrentProcess() .Modules.OfType() @@ -51,7 +50,6 @@ internal static unsafe class InjectorHelpers private static void CreateDefaultInjectedAssembly() { DefaultInjectedImage = CreateInjectedImage(InjectedMonoTypesAssemblyName); - DefaultInjectedAssembly = UnityVersionHandler.Wrap(DefaultInjectedImage.Assembly); } private static INativeImageStruct CreateInjectedImage(string name) @@ -60,36 +58,41 @@ private static INativeImageStruct CreateInjectedImage(string name) var assembly = UnityVersionHandler.NewAssembly(); var image = UnityVersionHandler.NewImage(); + var nameNoExt = name; + if (nameNoExt.EndsWith(".dll")) + nameNoExt = name.Replace(".dll", ""); - assembly.Name.Name = Marshal.StringToHGlobalAnsi(name); + assembly.Name.Name = Marshal.StringToHGlobalAnsi(nameNoExt); image.Assembly = assembly.AssemblyPointer; image.Dynamic = 1; - image.Name = assembly.Name.Name; + image.Name = Marshal.StringToHGlobalAnsi(name); if (image.HasNameNoExt) - { - if (name.EndsWith(".dll")) - image.NameNoExt = Marshal.StringToHGlobalAnsi(name.Replace(".dll", "")); - else - image.NameNoExt = assembly.Name.Name; - } + image.NameNoExt = assembly.Name.Name; assembly.Image = image.ImagePointer; InjectedImages.Add(name, image.Pointer); return image; } - internal static IntPtr GetOrCreateInjectedImage(string name) + internal static bool TryGetInjectedImage(string name, out IntPtr ptr) { - if (InjectedImages.TryGetValue(name, out var ptr)) - return ptr; - - return CreateInjectedImage(name).Pointer; + if (!name.EndsWith(".dll")) + name += ".dll"; + return InjectedImages.TryGetValue(name, out ptr); } internal static bool TryGetInjectedImageForAssembly(Assembly assembly, out IntPtr ptr) { - return InjectedImages.TryGetValue(assembly.GetName().Name, out ptr); + return TryGetInjectedImage(assembly.GetName().Name, out ptr); + } + + internal static IntPtr GetOrCreateInjectedImage(string name) + { + if (TryGetInjectedImage(name, out var ptr)) + return ptr; + + return CreateInjectedImage(name).Pointer; } private static readonly GenericMethod_GetMethod_Hook GenericMethodGetMethodHook = new(); @@ -114,7 +117,7 @@ internal static void Setup() FromIl2CppTypeHook.ApplyHook(); FromNameHook.ApplyHook(); RunFinalizerPatch.ApplyHook(); - assemblyLoadHook.ApplyHook(); + AssemblyGetLoadedAssemblyHook.ApplyHook(); AppDomainGetAssembliesHook.ApplyHook(); } @@ -146,7 +149,7 @@ internal static void EarlySetup() api_get_assemblies.ApplyHook(); } - internal static long CreateTypeToken(IntPtr classPointer) + internal static long CreateClassToken(IntPtr classPointer) { long newToken = Interlocked.Decrement(ref s_LastInjectedToken); s_InjectedClasses[newToken] = classPointer; @@ -159,7 +162,8 @@ internal static void AddTypeToLookup(Type type, IntPtr typePointer) string klass = type.Name; if (klass == null) return; string namespaze = type.Namespace ?? string.Empty; - var attribute = Attribute.GetCustomAttribute(type, typeof(Il2CppInterop.Runtime.Attributes.ClassInjectionAssemblyTargetAttribute)) as Il2CppInterop.Runtime.Attributes.ClassInjectionAssemblyTargetAttribute; + + var attribute = Attribute.GetCustomAttribute(type, typeof(Attributes.ClassInjectionAssemblyTargetAttribute)) as Attributes.ClassInjectionAssemblyTargetAttribute; foreach (IntPtr image in (attribute is null) ? IL2CPP.GetIl2CppImages() : attribute.GetImagePointers()) { From 6674ad52471d89faa47c60dc38b841cc56e8d99b Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Mon, 29 May 2023 20:26:30 +0300 Subject: [PATCH 05/13] Remove unnecessary changes --- .../ClassInjectionAssemblyTargetAttribute.cs | 42 ++----------------- Il2CppInterop.Runtime/IL2CPP.cs | 8 +--- 2 files changed, 6 insertions(+), 44 deletions(-) diff --git a/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs b/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs index b2cef54a..4916018c 100644 --- a/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs +++ b/Il2CppInterop.Runtime/Attributes/ClassInjectionAssemblyTargetAttribute.cs @@ -1,41 +1,23 @@ using System; using System.Collections.Generic; -using Il2CppInterop.Runtime.Injection; namespace Il2CppInterop.Runtime.Attributes; [AttributeUsage(AttributeTargets.Class)] public class ClassInjectionAssemblyTargetAttribute : Attribute { - private readonly AssemblyKind assemblyKind; private readonly string[] assemblies; public ClassInjectionAssemblyTargetAttribute(string assembly) { - if (string.IsNullOrWhiteSpace(assembly)) assemblies = Array.Empty(); + if (string.IsNullOrWhiteSpace(assembly)) assemblies = new string[0]; else assemblies = new[] { assembly }; - assemblyKind = AssemblyKind.IL2CPP; } public ClassInjectionAssemblyTargetAttribute(string[] assemblies) { - if (assemblies is null) this.assemblies = Array.Empty(); + if (assemblies is null) this.assemblies = new string[0]; else this.assemblies = assemblies; - assemblyKind = AssemblyKind.IL2CPP; - } - - public ClassInjectionAssemblyTargetAttribute(string assembly, AssemblyKind assemblyKind) - { - if (string.IsNullOrWhiteSpace(assembly)) assemblies = Array.Empty(); - else assemblies = new[] { assembly }; - this.assemblyKind = assemblyKind; - } - - public ClassInjectionAssemblyTargetAttribute(string[] assemblies, AssemblyKind assemblyKind) - { - if (assemblies is null) this.assemblies = Array.Empty(); - else this.assemblies = assemblies; - this.assemblyKind = assemblyKind; } internal IntPtr[] GetImagePointers() @@ -43,26 +25,10 @@ internal IntPtr[] GetImagePointers() var result = new List(); foreach (var assembly in assemblies) { - IntPtr intPtr; - switch (assemblyKind) - { - case AssemblyKind.IL2CPP: - intPtr = IL2CPP.GetIl2CppImage(assembly); - if (intPtr != IntPtr.Zero) result.Add(intPtr); - break; - case AssemblyKind.INJECTED: - intPtr = InjectorHelpers.GetOrCreateInjectedImage(assembly); - if (intPtr != IntPtr.Zero) result.Add(intPtr); - break; - } + var intPtr = IL2CPP.GetIl2CppImage(assembly); + if (intPtr != IntPtr.Zero) result.Add(intPtr); } return result.ToArray(); } } - -public enum AssemblyKind -{ - IL2CPP, - INJECTED -} diff --git a/Il2CppInterop.Runtime/IL2CPP.cs b/Il2CppInterop.Runtime/IL2CPP.cs index 5cfb0c97..04449ad5 100644 --- a/Il2CppInterop.Runtime/IL2CPP.cs +++ b/Il2CppInterop.Runtime/IL2CPP.cs @@ -43,7 +43,6 @@ static IL2CPP() internal static IntPtr GetIl2CppImage(string name) { if (il2CppImagesMap.ContainsKey(name)) return il2CppImagesMap[name]; - if (InjectorHelpers.InjectedImages.ContainsKey(name)) return InjectorHelpers.InjectedImages[name]; return IntPtr.Zero; } @@ -56,11 +55,8 @@ public static IntPtr GetIl2CppClass(string assemblyName, string namespaze, strin { if (!il2CppImagesMap.TryGetValue(assemblyName, out var image)) { - if (!InjectorHelpers.InjectedImages.TryGetValue(assemblyName, out image)) - { - Logger.Instance.LogError("Assembly {AssemblyName} is not registered in il2cpp", assemblyName); - return IntPtr.Zero; - } + Logger.Instance.LogError("Assembly {AssemblyName} is not registered in il2cpp", assemblyName); + return IntPtr.Zero; } var clazz = il2cpp_class_from_name(image, namespaze, className); From 18a545d1789584ccae348a7816c7d1634ebe8153 Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Mon, 29 May 2023 20:40:38 +0300 Subject: [PATCH 06/13] Fix formatting --- .../Injection/Hooks/AppDomain_GetAssemblies_Hook.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs index 411972df..8ae7fc13 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs @@ -60,7 +60,7 @@ public override IntPtr FindTargetMethod() if (appDomain == null) throw new Exception($"Unity {Il2CppInteropRuntime.Instance.UnityVersion} is not supported at the moment: System.AppDomain isn't present in Il2Cppmscorlib.dll for unity version, unable to fetch icall"); - var GetAssembliesThunk = InjectorHelpers.GetIl2CppMethodPointer(appDomain.GetMethod("GetAssemblies", unchecked((BindingFlags)0xffffffff), new[]{typeof(bool)})); + var GetAssembliesThunk = InjectorHelpers.GetIl2CppMethodPointer(appDomain.GetMethod("GetAssemblies", unchecked((BindingFlags)0xffffffff), new[] { typeof(bool) })); Logger.Instance.LogTrace("Il2CppSystem.AppDomain::thunk_GetAssemblies: 0x{GetAssembliesThunk}", GetAssembliesThunk.ToInt64().ToString("X2")); var myMethod = XrefScannerLowLevel.JumpTargets(GetAssembliesThunk).Single(); From 2f23a7b783dcc721770d93aebea3643c7baea922 Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Fri, 9 Jun 2023 08:45:59 +0300 Subject: [PATCH 07/13] Add IAT hooking to remove game file modification --- Il2CppInterop.Common/AssemblyIATHooker.cs | 640 ++++++++++++++++++ .../Injection/AssemblyListFile.cs | 27 +- .../Injection/Hooks/Assembly_Load_Hook.cs | 1 + .../Injection/InjectorHelpers.cs | 132 +++- 4 files changed, 768 insertions(+), 32 deletions(-) create mode 100644 Il2CppInterop.Common/AssemblyIATHooker.cs diff --git a/Il2CppInterop.Common/AssemblyIATHooker.cs b/Il2CppInterop.Common/AssemblyIATHooker.cs new file mode 100644 index 00000000..cb43c752 --- /dev/null +++ b/Il2CppInterop.Common/AssemblyIATHooker.cs @@ -0,0 +1,640 @@ +using System.Runtime.InteropServices; + +// ReSharper disable BuiltInTypeReferenceStyle + + +namespace Il2CppInterop.Common +{ + + /// + /// Reads in the header information of the Portable Executable format. + /// Provides information such as the date the assembly was compiled. + /// Also implements ability to create IAT hooks + /// + internal class AssemblyIATHooker { + #region File Header Structures + + public struct IMAGE_DOS_HEADER { // DOS .EXE header + public UInt16 e_magic; // Magic number + public UInt16 e_cblp; // Bytes on last page of file + public UInt16 e_cp; // Pages in file + public UInt16 e_crlc; // Relocations + public UInt16 e_cparhdr; // Size of header in paragraphs + public UInt16 e_minalloc; // Minimum extra paragraphs needed + public UInt16 e_maxalloc; // Maximum extra paragraphs needed + public UInt16 e_ss; // Initial (relative) SS value + public UInt16 e_sp; // Initial SP value + public UInt16 e_csum; // Checksum + public UInt16 e_ip; // Initial IP value + public UInt16 e_cs; // Initial (relative) CS value + public UInt16 e_lfarlc; // File address of relocation table + public UInt16 e_ovno; // Overlay number + public UInt16 e_res_0; // Reserved words + public UInt16 e_res_1; // Reserved words + public UInt16 e_res_2; // Reserved words + public UInt16 e_res_3; // Reserved words + public UInt16 e_oemid; // OEM identifier (for e_oeminfo) + public UInt16 e_oeminfo; // OEM information; e_oemid specific + public UInt16 e_res2_0; // Reserved words + public UInt16 e_res2_1; // Reserved words + public UInt16 e_res2_2; // Reserved words + public UInt16 e_res2_3; // Reserved words + public UInt16 e_res2_4; // Reserved words + public UInt16 e_res2_5; // Reserved words + public UInt16 e_res2_6; // Reserved words + public UInt16 e_res2_7; // Reserved words + public UInt16 e_res2_8; // Reserved words + public UInt16 e_res2_9; // Reserved words + public UInt32 e_lfanew; // File address of new exe header + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DATA_DIRECTORY { + public UInt32 VirtualAddress; + public UInt32 Size; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER32 { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt32 BaseOfData; + public UInt32 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt32 SizeOfStackReserve; + public UInt32 SizeOfStackCommit; + public UInt32 SizeOfHeapReserve; + public UInt32 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER64 { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt64 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt64 SizeOfStackReserve; + public UInt64 SizeOfStackCommit; + public UInt64 SizeOfHeapReserve; + public UInt64 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_FILE_HEADER { + public UInt16 Machine; + public UInt16 NumberOfSections; + public UInt32 TimeDateStamp; + public UInt32 PointerToSymbolTable; + public UInt32 NumberOfSymbols; + public UInt16 SizeOfOptionalHeader; + public UInt16 Characteristics; + } + + // Grabbed the following 2 definitions from http://www.pinvoke.net/default.aspx/Structures/IMAGE_SECTION_HEADER.html + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_SECTION_HEADER { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] Name; + [FieldOffset(8)] + public UInt32 VirtualSize; + [FieldOffset(12)] + public UInt32 VirtualAddress; + [FieldOffset(16)] + public UInt32 SizeOfRawData; + [FieldOffset(20)] + public UInt32 PointerToRawData; + [FieldOffset(24)] + public UInt32 PointerToRelocations; + [FieldOffset(28)] + public UInt32 PointerToLinenumbers; + [FieldOffset(32)] + public UInt16 NumberOfRelocations; + [FieldOffset(34)] + public UInt16 NumberOfLinenumbers; + [FieldOffset(36)] + public DataSectionFlags Characteristics; + + public string Section { + get { return new string(Name); } + } + } + + [Flags] + public enum DataSectionFlags : uint { + /// + /// Reserved for future use. + /// + TypeReg = 0x00000000, + /// + /// Reserved for future use. + /// + TypeDsect = 0x00000001, + /// + /// Reserved for future use. + /// + TypeNoLoad = 0x00000002, + /// + /// Reserved for future use. + /// + TypeGroup = 0x00000004, + /// + /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. + /// + TypeNoPadded = 0x00000008, + /// + /// Reserved for future use. + /// + TypeCopy = 0x00000010, + /// + /// The section contains executable code. + /// + ContentCode = 0x00000020, + /// + /// The section contains initialized data. + /// + ContentInitializedData = 0x00000040, + /// + /// The section contains uninitialized data. + /// + ContentUninitializedData = 0x00000080, + /// + /// Reserved for future use. + /// + LinkOther = 0x00000100, + /// + /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. + /// + LinkInfo = 0x00000200, + /// + /// Reserved for future use. + /// + TypeOver = 0x00000400, + /// + /// The section will not become part of the image. This is valid only for object files. + /// + LinkRemove = 0x00000800, + /// + /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. + /// + LinkComDat = 0x00001000, + /// + /// Reset speculative exceptions handling bits in the TLB entries for this section. + /// + NoDeferSpecExceptions = 0x00004000, + /// + /// The section contains data referenced through the global pointer (GP). + /// + RelativeGP = 0x00008000, + /// + /// Reserved for future use. + /// + MemPurgeable = 0x00020000, + /// + /// Reserved for future use. + /// + Memory16Bit = 0x00020000, + /// + /// Reserved for future use. + /// + MemoryLocked = 0x00040000, + /// + /// Reserved for future use. + /// + MemoryPreload = 0x00080000, + /// + /// Align data on a 1-byte boundary. Valid only for object files. + /// + Align1Bytes = 0x00100000, + /// + /// Align data on a 2-byte boundary. Valid only for object files. + /// + Align2Bytes = 0x00200000, + /// + /// Align data on a 4-byte boundary. Valid only for object files. + /// + Align4Bytes = 0x00300000, + /// + /// Align data on an 8-byte boundary. Valid only for object files. + /// + Align8Bytes = 0x00400000, + /// + /// Align data on a 16-byte boundary. Valid only for object files. + /// + Align16Bytes = 0x00500000, + /// + /// Align data on a 32-byte boundary. Valid only for object files. + /// + Align32Bytes = 0x00600000, + /// + /// Align data on a 64-byte boundary. Valid only for object files. + /// + Align64Bytes = 0x00700000, + /// + /// Align data on a 128-byte boundary. Valid only for object files. + /// + Align128Bytes = 0x00800000, + /// + /// Align data on a 256-byte boundary. Valid only for object files. + /// + Align256Bytes = 0x00900000, + /// + /// Align data on a 512-byte boundary. Valid only for object files. + /// + Align512Bytes = 0x00A00000, + /// + /// Align data on a 1024-byte boundary. Valid only for object files. + /// + Align1024Bytes = 0x00B00000, + /// + /// Align data on a 2048-byte boundary. Valid only for object files. + /// + Align2048Bytes = 0x00C00000, + /// + /// Align data on a 4096-byte boundary. Valid only for object files. + /// + Align4096Bytes = 0x00D00000, + /// + /// Align data on an 8192-byte boundary. Valid only for object files. + /// + Align8192Bytes = 0x00E00000, + /// + /// The section contains extended relocations. + /// + LinkExtendedRelocationOverflow = 0x01000000, + /// + /// The section can be discarded as needed. + /// + MemoryDiscardable = 0x02000000, + /// + /// The section cannot be cached. + /// + MemoryNotCached = 0x04000000, + /// + /// The section is not pageable. + /// + MemoryNotPaged = 0x08000000, + /// + /// The section can be shared in memory. + /// + MemoryShared = 0x10000000, + /// + /// The section can be executed as code. + /// + MemoryExecute = 0x20000000, + /// + /// The section can be read. + /// + MemoryRead = 0x40000000, + /// + /// The section can be written to. + /// + MemoryWrite = 0x80000000 + } + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_IMPORT_DESCRIPTOR + { + [FieldOffset(0)] + public uint Characteristics; + + [FieldOffset(0)] + public uint OriginalFirstThunk; + + [FieldOffset(4)] + public uint TimeDateStamp; + + [FieldOffset(8)] + public uint ForwarderChain; + + [FieldOffset(12)] + public uint Name; + + [FieldOffset(16)] + public uint FirstThunk; + } + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_THUNK_DATA + { + [FieldOffset(0)] + public IntPtr ForwarderString; + + [FieldOffset(0)] + public IntPtr Function; + + [FieldOffset(0)] + public IntPtr Ordinal; + + [FieldOffset(0)] + public IntPtr AddressOfData; + } + + public unsafe struct HookData + { + public IMAGE_THUNK_DATA* thunk; + public IntPtr originalFunction; + + public HookData(IMAGE_THUNK_DATA* thunk) + { + this.thunk = thunk; + originalFunction = thunk->Function; + } + } + + #endregion File Header Structures + + #region Private Fields + + private IntPtr imageBase; + + /// + /// The DOS header + /// + private IMAGE_DOS_HEADER dosHeader; + /// + /// The file header + /// + private IMAGE_FILE_HEADER fileHeader; + /// + /// Optional 32 bit file header + /// + private IMAGE_OPTIONAL_HEADER32 optionalHeader32; + /// + /// Optional 64 bit file header + /// + private IMAGE_OPTIONAL_HEADER64 optionalHeader64; + /// + /// Image Section headers. Number of sections is in the file header. + /// + private IMAGE_SECTION_HEADER[] imageSectionHeaders; + + private Dictionary, HookData> hookThunks = new Dictionary, HookData>(); + + #endregion Private Fields + + #region Public Methods + + public unsafe AssemblyIATHooker(IntPtr imageBase) + { + this.imageBase = imageBase; + var stream = new UnmanagedMemoryStream((byte*)imageBase, 5000, 5000, FileAccess.ReadWrite); + Create(stream); + } + + private void Create(Stream stream) + { + BinaryReader reader = new BinaryReader(stream); + dosHeader = FromBinaryReader(reader); + + // Add 4 bytes to the offset + stream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin); + + UInt32 ntHeadersSignature = reader.ReadUInt32(); + fileHeader = FromBinaryReader(reader); + if (Is32BitHeader) + { + optionalHeader32 = FromBinaryReader(reader); + } + else + { + optionalHeader64 = FromBinaryReader(reader); + } + + imageSectionHeaders = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections]; + for (int headerNo = 0; headerNo < imageSectionHeaders.Length; ++headerNo) + { + imageSectionHeaders[headerNo] = FromBinaryReader(reader); + } + } + + /// + /// Reads in a block from a file and converts it to the struct + /// type specified by the template parameter + /// + /// + /// + /// + public static T FromBinaryReader(BinaryReader reader) { + // Read in a byte array + byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); + + // Pin the managed memory while, copy it out the data, then unpin it + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + handle.Free(); + + return theStructure; + } + + /// + /// Gets if the file header is 32 bit or not + /// + public bool Is32BitHeader { + get { + UInt16 IMAGE_FILE_32BIT_MACHINE = 0x0100; + return (IMAGE_FILE_32BIT_MACHINE & FileHeader.Characteristics) == IMAGE_FILE_32BIT_MACHINE; + } + } + + /// + /// Gets the file header + /// + public IMAGE_FILE_HEADER FileHeader { + get { + return fileHeader; + } + } + + /// + /// Gets the optional header + /// + public IMAGE_OPTIONAL_HEADER32 OptionalHeader32 { + get { + return optionalHeader32; + } + } + + /// + /// Gets the optional header + /// + public IMAGE_OPTIONAL_HEADER64 OptionalHeader64 { + get { + return optionalHeader64; + } + } + + public IMAGE_SECTION_HEADER[] ImageSectionHeaders { + get { + return imageSectionHeaders; + } + } + + public IMAGE_DATA_DIRECTORY ImportsDirectory + { + get + { + if (Is32BitHeader) + { + return optionalHeader32.ImportTable; + } + + return optionalHeader64.ImportTable; + } + } + + public enum ProtectMode + { + PAGE_NOACCESS = 0x1, + PAGE_READONLY = 0x2, + PAGE_READWRITE = 0x4, + PAGE_WRITECOPY = 0x8, + PAGE_EXECUTE = 0x10, + PAGE_EXECUTE_READ = 0x20, + PAGE_EXECUTE_READWRITE = 0x40, + PAGE_EXECUTE_WRITECOPY = 0x80 + } + + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); + + private static bool VirtualProtect(IntPtr lpAdress, int dwSize, ProtectMode flNewProtect, out ProtectMode lpflOldProtect) + { + bool result = VirtualProtect(lpAdress, (IntPtr)dwSize, (uint)flNewProtect, out uint oldProtect); + lpflOldProtect = (ProtectMode)oldProtect; + return result; + } + + public unsafe delegate void ApplyAction(IMAGE_THUNK_DATA* thunk); + + public unsafe void CreateIATHook(string targetLibrary, string targetFunction, ApplyAction onFound) + { + var importDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(imageBase + (int)ImportsDirectory.VirtualAddress); + + while (importDescriptor->Name != 0) + { + var libraryNamePtr = imageBase + (int)importDescriptor->Name; + var libraryName = Marshal.PtrToStringAnsi(libraryNamePtr); + + if (libraryName.Equals(targetLibrary)) + { + var originalFirstThunk = (IMAGE_THUNK_DATA*)(imageBase + (int)importDescriptor->OriginalFirstThunk); + var firstThunk = (IMAGE_THUNK_DATA*)(imageBase + (int)importDescriptor->FirstThunk); + + while (originalFirstThunk->AddressOfData != IntPtr.Zero) + { + var functionNamePtr = (imageBase + (int)originalFirstThunk->AddressOfData); + var functionName = Marshal.PtrToStringAnsi(functionNamePtr + 2); + + if (functionName.Equals(targetFunction)) + { + IntPtr address = (IntPtr)(&firstThunk->Function); + VirtualProtect(address, 8, ProtectMode.PAGE_READWRITE, out ProtectMode oldProtect); + var key = new Tuple(targetLibrary, targetFunction); + + if (!hookThunks.ContainsKey(key)) + hookThunks.Add(key, new HookData(firstThunk)); + onFound.Invoke(firstThunk); + break; + } + ++originalFirstThunk; + ++firstThunk; + } + break; + } + + importDescriptor++; + } + } + + public unsafe void UnpatchIATHook(string targetLibrary, string targetFunction) + { + var key = new Tuple(targetLibrary, targetFunction); + if (hookThunks.ContainsKey(key)) + { + HookData hook = hookThunks[key]; + hook.thunk->Function = hook.originalFunction; + hookThunks.Remove(key); + } + } + + #endregion Properties + } +} diff --git a/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs b/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs index 633aca9f..39086ece 100644 --- a/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs +++ b/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs @@ -4,25 +4,17 @@ namespace Il2CppInterop.Runtime.Injection { - public class AssemblyListFile : IDisposable + public class AssemblyListFile { - private readonly string assemblyNamesFile; private readonly JsonNode node; private readonly JsonArray names; private readonly JsonArray types; - public AssemblyListFile() - { - var executablePath = Environment.GetEnvironmentVariable("DOORSTOP_PROCESS_PATH"); - var processPath = Path.GetFileNameWithoutExtension(executablePath); - assemblyNamesFile = $"{processPath}_Data/ScriptingAssemblies.json"; - var assemblyNamesFileBackup = $"{processPath}_Data/ScriptingAssemblies_BACKUP.json"; - if (!File.Exists(assemblyNamesFileBackup)) - { - File.Copy(assemblyNamesFile, assemblyNamesFileBackup); - } + private string newFile; - node = JsonNode.Parse(File.ReadAllText(assemblyNamesFileBackup)); + public AssemblyListFile(string originalFilePath) + { + node = JsonNode.Parse(File.ReadAllText(originalFilePath)); names = node["names"].AsArray(); types = node["types"].AsArray(); } @@ -33,10 +25,15 @@ public void AddAssembly(string name) types.Add(16); } - public void Dispose() + public string GetTmpFile() { + if (!string.IsNullOrEmpty(newFile)) return newFile; + var newJson = node.ToJsonString(); - File.WriteAllText(assemblyNamesFile, newJson); + newFile = Path.GetTempFileName(); + + File.WriteAllText(newFile, newJson); + return newFile; } } } diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs index 21d6243b..abd8ad96 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs @@ -23,6 +23,7 @@ internal unsafe class Assembly_Load_Hook : Hook InjectedImages = new Dictionary(); internal static INativeImageStruct DefaultInjectedImage; + internal static ProcessModule Il2CppModule = Process.GetCurrentProcess() .Modules.OfType() .Single((x) => x.ModuleName is "GameAssembly.dll" or "GameAssembly.so" or "UserAssembly.dll"); internal static IntPtr Il2CppHandle = NativeLibrary.Load("GameAssembly", typeof(InjectorHelpers).Assembly, null); + internal static IntPtr UnityPlayerHandle = NativeLibrary.Load("UnityPlayer", typeof(InjectorHelpers).Assembly, null); + internal static AssemblyIATHooker UnityPlayerIATHooker; + internal static string NewAssemblyListFile; internal static readonly Dictionary StIndOpcodes = new() { @@ -122,11 +127,45 @@ internal static void Setup() AppDomainGetAssembliesHook.ApplyHook(); } - // Setup before unity loads assembly list - internal static void EarlySetup() + [StructLayout(LayoutKind.Sequential)] + public struct WIN32_FILE_ATTRIBUTE_DATA { - using (AssemblyListFile assemblyList = new()) + public FileAttributes dwFileAttributes; + public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; + public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + } + + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + static extern uint GetFinalPathNameByHandle(IntPtr hFile, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpszFilePath, uint cchFilePath, uint dwFlags); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr CreateFile( + [MarshalAs(UnmanagedType.LPTStr)] string filename, + [MarshalAs(UnmanagedType.U4)] FileAccess access, + [MarshalAs(UnmanagedType.U4)] FileShare share, + IntPtr securityAttributes, // optional SECURITY_ATTRIBUTES struct or IntPtr.Zero + [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes, + IntPtr templateFile); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int d_GetFileAttributesEx(IntPtr lpFileName, int fInfoLevelId, IntPtr lpFileInformation); + + internal static d_GetFileAttributesEx GetFileAttributesEx; + + private static int GetFileAttributesExDetour(IntPtr lpFileName, int fInfoLevelId, IntPtr lpFileInformation) + { + var filePath = Marshal.PtrToStringUni(lpFileName); + + if (filePath.Contains("ScriptingAssemblies.json")) { + filePath = filePath.Replace(@"\\?\", ""); + + var assemblyList = new AssemblyListFile(filePath); + CreateDefaultInjectedAssembly(); assemblyList.AddAssembly(InjectedMonoTypesAssemblyName); var coreFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); @@ -143,8 +182,66 @@ internal static void EarlySetup() assemblyList.AddAssembly(assemblyName); } } + + NewAssemblyListFile = assemblyList.GetTmpFile(); + Logger.Instance.LogInformation($"Forcing unity to read assembly list from {NewAssemblyListFile}"); + var newlpFileName = Marshal.StringToHGlobalUni(NewAssemblyListFile); + + var result = GetFileAttributesEx(newlpFileName, fInfoLevelId, lpFileInformation); + Marshal.FreeHGlobal(newlpFileName); + return result; } + return GetFileAttributesEx(lpFileName, fInfoLevelId, lpFileInformation); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int d_ReadFile(IntPtr handle, IntPtr bytes, uint numBytesToRead, IntPtr numBytesRead, NativeOverlapped* overlapped); + + internal static d_ReadFile ReadFile; + + private static int ReadFileDetour(IntPtr handle, IntPtr bytes, uint numBytesToRead, IntPtr numBytesRead, NativeOverlapped* overlapped) + { + var sb = new StringBuilder(1024); + var res = GetFinalPathNameByHandle(handle, sb, 1024, 0); + + if (res != 0) + { + var filePath = sb.ToString(); + if (filePath.Contains("ScriptingAssemblies.json")) + { + IntPtr newHandle = CreateFile(NewAssemblyListFile, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); + UnpatchIATHooks(); + return ReadFile(newHandle, bytes, numBytesToRead, numBytesRead, overlapped); + } + } + + return ReadFile(handle, bytes, numBytesToRead, numBytesRead, overlapped); + } + + private static void UnpatchIATHooks() + { + Logger.Instance.LogInformation("Unpatching UnityPlayer IAT hooks"); + UnityPlayerIATHooker.UnpatchIATHook("KERNEL32.dll", "ReadFile"); + UnityPlayerIATHooker.UnpatchIATHook("KERNEL32.dll", "GetFileAttributesExW"); + } + + // Setup before unity loads assembly list + internal static void EarlySetup() + { + UnityPlayerIATHooker = new AssemblyIATHooker(UnityPlayerHandle); + UnityPlayerIATHooker.CreateIATHook("KERNEL32.dll", "ReadFile", thunk => + { + ReadFile = Marshal.GetDelegateForFunctionPointer(thunk->Function); + thunk->Function = Marshal.GetFunctionPointerForDelegate(ReadFileDetour); + }); + + UnityPlayerIATHooker.CreateIATHook("KERNEL32.dll", "GetFileAttributesExW", thunk => + { + GetFileAttributesEx = Marshal.GetDelegateForFunctionPointer(thunk->Function); + thunk->Function = Marshal.GetFunctionPointerForDelegate(GetFileAttributesExDetour); + }); + assemblyLoadHook.ApplyHook(); api_get_assemblies.ApplyHook(); } @@ -157,13 +254,16 @@ internal static long CreateClassToken(IntPtr classPointer) } internal static void AddTypeToLookup(IntPtr typePointer) where T : class => AddTypeToLookup(typeof(T), typePointer); + internal static void AddTypeToLookup(Type type, IntPtr typePointer) { string klass = type.Name; if (klass == null) return; string namespaze = type.Namespace ?? string.Empty; - var attribute = Attribute.GetCustomAttribute(type, typeof(Attributes.ClassInjectionAssemblyTargetAttribute)) as Attributes.ClassInjectionAssemblyTargetAttribute; + var attribute = + Attribute.GetCustomAttribute(type, + typeof(Attributes.ClassInjectionAssemblyTargetAttribute)) as Attributes.ClassInjectionAssemblyTargetAttribute; foreach (IntPtr image in (attribute is null) ? IL2CPP.GetIl2CppImages() : attribute.GetImagePointers()) { @@ -206,28 +306,21 @@ internal static IntPtr GetIl2CppMethodPointer(MethodBase proxyMethod) private static long s_LastInjectedToken = -2; internal static readonly ConcurrentDictionary s_InjectedClasses = new(); + /// (namespace, class, image) : class internal static readonly Dictionary<(string _namespace, string _class, IntPtr imagePtr), IntPtr> s_ClassNameLookup = new(); #region Class::Init + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void d_ClassInit(Il2CppClass* klass); + internal static d_ClassInit ClassInit; private static readonly MemoryUtils.SignatureDefinition[] s_ClassInitSignatures = { - new MemoryUtils.SignatureDefinition - { - pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x28\x83", - mask = "x????xxxxx", - xref = true - }, - new MemoryUtils.SignatureDefinition - { - pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x48\x48", - mask = "x????xxxxx", - xref = true - } + new MemoryUtils.SignatureDefinition { pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x28\x83", mask = "x????xxxxx", xref = true }, + new MemoryUtils.SignatureDefinition { pattern = "\xE8\x00\x00\x00\x00\x0F\xB7\x47\x48\x48", mask = "x????xxxxx", xref = true } }; private static d_ClassInit FindClassInit() @@ -239,11 +332,13 @@ static nint GetClassInitSubstitute() Logger.Instance.LogTrace("Picked mono_class_instance_size as a Class::Init substitute"); return classInit; } + if (TryGetIl2CppExport("mono_class_setup_vtable", out classInit)) { Logger.Instance.LogTrace("Picked mono_class_setup_vtable as a Class::Init substitute"); return classInit; } + if (TryGetIl2CppExport(nameof(IL2CPP.il2cpp_class_has_references), out classInit)) { Logger.Instance.LogTrace("Picked il2cpp_class_has_references as a Class::Init substitute"); @@ -251,8 +346,10 @@ static nint GetClassInitSubstitute() } Logger.Instance.LogTrace("GameAssembly.dll: 0x{Il2CppModuleAddress}", Il2CppModule.BaseAddress.ToInt64().ToString("X2")); - throw new NotSupportedException("Failed to use signature for Class::Init and a substitute cannot be found, please create an issue and report your unity version & game"); + throw new NotSupportedException( + "Failed to use signature for Class::Init and a substitute cannot be found, please create an issue and report your unity version & game"); } + nint pClassInit = s_ClassInitSignatures .Select(s => MemoryUtils.FindSignatureInModule(Il2CppModule, s)) .FirstOrDefault(p => p != 0); @@ -267,6 +364,7 @@ static nint GetClassInitSubstitute() return Marshal.GetDelegateForFunctionPointer(pClassInit); } + #endregion } } From bbcc92146d8c84cb4af928bb1d512b0df8a35e2f Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Sat, 10 Jun 2023 09:43:48 +0300 Subject: [PATCH 08/13] Use component to remove hardcoded assembly lookup. --- .../Injection/AssemblyInjectorComponent.cs | 53 +++++++++++++++++++ .../Injection/InjectorHelpers.cs | 7 ++- .../Startup/Il2CppInteropRuntime.cs | 4 +- 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 Il2CppInterop.Runtime/Injection/AssemblyInjectorComponent.cs diff --git a/Il2CppInterop.Runtime/Injection/AssemblyInjectorComponent.cs b/Il2CppInterop.Runtime/Injection/AssemblyInjectorComponent.cs new file mode 100644 index 00000000..843edce9 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/AssemblyInjectorComponent.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using Il2CppInterop.Common.Host; +using Il2CppInterop.Runtime.Injection; + +namespace Il2CppInterop.Runtime +{ + internal static class AssemblyListComponentExtensions + { + public static T AddAssemblyInjector(this T host) + where T : BaseHost + where TProvider : IAssemblyListProvider, new() + { + host.AddComponent(new AssemblyInjectorComponent(new TProvider())); + return host; + } + } + + public interface IAssemblyListProvider + { + public IEnumerable GetAssemblyList(); + } + + public class AssemblyInjectorComponent : IHostComponent + { + private static IAssemblyListProvider s_assemblyListProvider; + + public AssemblyInjectorComponent(IAssemblyListProvider assemblyListProvider) => s_assemblyListProvider = assemblyListProvider; + + public static IEnumerable ModAssemblies + { + get + { + if (s_assemblyListProvider == null) + { + throw new InvalidOperationException("Mod Assembly Injector is not initialized! Initialize the host before using Mod Assembly Injector!"); + } + + return s_assemblyListProvider.GetAssemblyList(); + } + } + + public void Dispose() + { + s_assemblyListProvider = null; + } + + public void Start() + { + InjectorHelpers.EarlySetup(); + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs index f7e2cd3d..659e8f43 100644 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs @@ -54,7 +54,7 @@ internal static unsafe class InjectorHelpers private static void CreateDefaultInjectedAssembly() { - DefaultInjectedImage = CreateInjectedImage(InjectedMonoTypesAssemblyName); + DefaultInjectedImage ??= CreateInjectedImage(InjectedMonoTypesAssemblyName); } private static INativeImageStruct CreateInjectedImage(string name) @@ -115,6 +115,7 @@ internal static IntPtr GetOrCreateInjectedImage(string name) internal static void Setup() { + CreateDefaultInjectedAssembly(); GenericMethodGetMethodHook.ApplyHook(); GetTypeInfoFromTypeDefinitionIndexHook.ApplyHook(); GetFieldDefaultValueHook.ApplyHook(); @@ -168,10 +169,8 @@ private static int GetFileAttributesExDetour(IntPtr lpFileName, int fInfoLevelId CreateDefaultInjectedAssembly(); assemblyList.AddAssembly(InjectedMonoTypesAssemblyName); - var coreFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - var pluginsFolder = Path.Combine(coreFolder, "../plugins/"); + var allFiles = AssemblyInjectorComponent.ModAssemblies; - var allFiles = Directory.EnumerateFiles(pluginsFolder, "*", SearchOption.AllDirectories); foreach (var file in allFiles) { var extension = Path.GetExtension(file); diff --git a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs index 1492a71a..5f9379a0 100644 --- a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs +++ b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; using Il2CppInterop.Common.Host; using Il2CppInterop.Common.XrefScans; using Il2CppInterop.Runtime.Injection; @@ -40,7 +43,6 @@ public static Il2CppInteropRuntime Create(RuntimeConfiguration configuration) public override void Start() { UnityVersionHandler.RecalculateHandlers(); - InjectorHelpers.EarlySetup(); base.Start(); } } From 3a0d6215bd475351f1d5c1ab91240b6dd922016e Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Sat, 10 Jun 2023 09:45:29 +0300 Subject: [PATCH 09/13] Cleanup and format --- Il2CppInterop.Common/AssemblyIATHooker.cs | 1150 +++++++++-------- .../Injection/Hooks/Assembly_Load_Hook.cs | 1 - .../Injection/InjectorHelpers.cs | 2 +- 3 files changed, 586 insertions(+), 567 deletions(-) diff --git a/Il2CppInterop.Common/AssemblyIATHooker.cs b/Il2CppInterop.Common/AssemblyIATHooker.cs index cb43c752..93b8f0dc 100644 --- a/Il2CppInterop.Common/AssemblyIATHooker.cs +++ b/Il2CppInterop.Common/AssemblyIATHooker.cs @@ -6,635 +6,655 @@ namespace Il2CppInterop.Common { - /// - /// Reads in the header information of the Portable Executable format. - /// Provides information such as the date the assembly was compiled. - /// Also implements ability to create IAT hooks - /// - internal class AssemblyIATHooker { - #region File Header Structures - - public struct IMAGE_DOS_HEADER { // DOS .EXE header - public UInt16 e_magic; // Magic number - public UInt16 e_cblp; // Bytes on last page of file - public UInt16 e_cp; // Pages in file - public UInt16 e_crlc; // Relocations - public UInt16 e_cparhdr; // Size of header in paragraphs - public UInt16 e_minalloc; // Minimum extra paragraphs needed - public UInt16 e_maxalloc; // Maximum extra paragraphs needed - public UInt16 e_ss; // Initial (relative) SS value - public UInt16 e_sp; // Initial SP value - public UInt16 e_csum; // Checksum - public UInt16 e_ip; // Initial IP value - public UInt16 e_cs; // Initial (relative) CS value - public UInt16 e_lfarlc; // File address of relocation table - public UInt16 e_ovno; // Overlay number - public UInt16 e_res_0; // Reserved words - public UInt16 e_res_1; // Reserved words - public UInt16 e_res_2; // Reserved words - public UInt16 e_res_3; // Reserved words - public UInt16 e_oemid; // OEM identifier (for e_oeminfo) - public UInt16 e_oeminfo; // OEM information; e_oemid specific - public UInt16 e_res2_0; // Reserved words - public UInt16 e_res2_1; // Reserved words - public UInt16 e_res2_2; // Reserved words - public UInt16 e_res2_3; // Reserved words - public UInt16 e_res2_4; // Reserved words - public UInt16 e_res2_5; // Reserved words - public UInt16 e_res2_6; // Reserved words - public UInt16 e_res2_7; // Reserved words - public UInt16 e_res2_8; // Reserved words - public UInt16 e_res2_9; // Reserved words - public UInt32 e_lfanew; // File address of new exe header - } - - [StructLayout(LayoutKind.Sequential)] - public struct IMAGE_DATA_DIRECTORY { - public UInt32 VirtualAddress; - public UInt32 Size; - } + /// + /// Reads in the header information of the Portable Executable format. + /// Provides information such as the date the assembly was compiled. + /// Also implements ability to create IAT hooks + /// + internal class AssemblyIATHooker + { + #region File Header Structures + + public struct IMAGE_DOS_HEADER + { // DOS .EXE header + public UInt16 e_magic; // Magic number + public UInt16 e_cblp; // Bytes on last page of file + public UInt16 e_cp; // Pages in file + public UInt16 e_crlc; // Relocations + public UInt16 e_cparhdr; // Size of header in paragraphs + public UInt16 e_minalloc; // Minimum extra paragraphs needed + public UInt16 e_maxalloc; // Maximum extra paragraphs needed + public UInt16 e_ss; // Initial (relative) SS value + public UInt16 e_sp; // Initial SP value + public UInt16 e_csum; // Checksum + public UInt16 e_ip; // Initial IP value + public UInt16 e_cs; // Initial (relative) CS value + public UInt16 e_lfarlc; // File address of relocation table + public UInt16 e_ovno; // Overlay number + public UInt16 e_res_0; // Reserved words + public UInt16 e_res_1; // Reserved words + public UInt16 e_res_2; // Reserved words + public UInt16 e_res_3; // Reserved words + public UInt16 e_oemid; // OEM identifier (for e_oeminfo) + public UInt16 e_oeminfo; // OEM information; e_oemid specific + public UInt16 e_res2_0; // Reserved words + public UInt16 e_res2_1; // Reserved words + public UInt16 e_res2_2; // Reserved words + public UInt16 e_res2_3; // Reserved words + public UInt16 e_res2_4; // Reserved words + public UInt16 e_res2_5; // Reserved words + public UInt16 e_res2_6; // Reserved words + public UInt16 e_res2_7; // Reserved words + public UInt16 e_res2_8; // Reserved words + public UInt16 e_res2_9; // Reserved words + public UInt32 e_lfanew; // File address of new exe header + } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_OPTIONAL_HEADER32 { - public UInt16 Magic; - public Byte MajorLinkerVersion; - public Byte MinorLinkerVersion; - public UInt32 SizeOfCode; - public UInt32 SizeOfInitializedData; - public UInt32 SizeOfUninitializedData; - public UInt32 AddressOfEntryPoint; - public UInt32 BaseOfCode; - public UInt32 BaseOfData; - public UInt32 ImageBase; - public UInt32 SectionAlignment; - public UInt32 FileAlignment; - public UInt16 MajorOperatingSystemVersion; - public UInt16 MinorOperatingSystemVersion; - public UInt16 MajorImageVersion; - public UInt16 MinorImageVersion; - public UInt16 MajorSubsystemVersion; - public UInt16 MinorSubsystemVersion; - public UInt32 Win32VersionValue; - public UInt32 SizeOfImage; - public UInt32 SizeOfHeaders; - public UInt32 CheckSum; - public UInt16 Subsystem; - public UInt16 DllCharacteristics; - public UInt32 SizeOfStackReserve; - public UInt32 SizeOfStackCommit; - public UInt32 SizeOfHeapReserve; - public UInt32 SizeOfHeapCommit; - public UInt32 LoaderFlags; - public UInt32 NumberOfRvaAndSizes; - - public IMAGE_DATA_DIRECTORY ExportTable; - public IMAGE_DATA_DIRECTORY ImportTable; - public IMAGE_DATA_DIRECTORY ResourceTable; - public IMAGE_DATA_DIRECTORY ExceptionTable; - public IMAGE_DATA_DIRECTORY CertificateTable; - public IMAGE_DATA_DIRECTORY BaseRelocationTable; - public IMAGE_DATA_DIRECTORY Debug; - public IMAGE_DATA_DIRECTORY Architecture; - public IMAGE_DATA_DIRECTORY GlobalPtr; - public IMAGE_DATA_DIRECTORY TLSTable; - public IMAGE_DATA_DIRECTORY LoadConfigTable; - public IMAGE_DATA_DIRECTORY BoundImport; - public IMAGE_DATA_DIRECTORY IAT; - public IMAGE_DATA_DIRECTORY DelayImportDescriptor; - public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; - public IMAGE_DATA_DIRECTORY Reserved; - } + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DATA_DIRECTORY + { + public UInt32 VirtualAddress; + public UInt32 Size; + } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_OPTIONAL_HEADER64 { - public UInt16 Magic; - public Byte MajorLinkerVersion; - public Byte MinorLinkerVersion; - public UInt32 SizeOfCode; - public UInt32 SizeOfInitializedData; - public UInt32 SizeOfUninitializedData; - public UInt32 AddressOfEntryPoint; - public UInt32 BaseOfCode; - public UInt64 ImageBase; - public UInt32 SectionAlignment; - public UInt32 FileAlignment; - public UInt16 MajorOperatingSystemVersion; - public UInt16 MinorOperatingSystemVersion; - public UInt16 MajorImageVersion; - public UInt16 MinorImageVersion; - public UInt16 MajorSubsystemVersion; - public UInt16 MinorSubsystemVersion; - public UInt32 Win32VersionValue; - public UInt32 SizeOfImage; - public UInt32 SizeOfHeaders; - public UInt32 CheckSum; - public UInt16 Subsystem; - public UInt16 DllCharacteristics; - public UInt64 SizeOfStackReserve; - public UInt64 SizeOfStackCommit; - public UInt64 SizeOfHeapReserve; - public UInt64 SizeOfHeapCommit; - public UInt32 LoaderFlags; - public UInt32 NumberOfRvaAndSizes; - - public IMAGE_DATA_DIRECTORY ExportTable; - public IMAGE_DATA_DIRECTORY ImportTable; - public IMAGE_DATA_DIRECTORY ResourceTable; - public IMAGE_DATA_DIRECTORY ExceptionTable; - public IMAGE_DATA_DIRECTORY CertificateTable; - public IMAGE_DATA_DIRECTORY BaseRelocationTable; - public IMAGE_DATA_DIRECTORY Debug; - public IMAGE_DATA_DIRECTORY Architecture; - public IMAGE_DATA_DIRECTORY GlobalPtr; - public IMAGE_DATA_DIRECTORY TLSTable; - public IMAGE_DATA_DIRECTORY LoadConfigTable; - public IMAGE_DATA_DIRECTORY BoundImport; - public IMAGE_DATA_DIRECTORY IAT; - public IMAGE_DATA_DIRECTORY DelayImportDescriptor; - public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; - public IMAGE_DATA_DIRECTORY Reserved; - } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER32 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt32 BaseOfData; + public UInt32 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt32 SizeOfStackReserve; + public UInt32 SizeOfStackCommit; + public UInt32 SizeOfHeapReserve; + public UInt32 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_FILE_HEADER { - public UInt16 Machine; - public UInt16 NumberOfSections; - public UInt32 TimeDateStamp; - public UInt32 PointerToSymbolTable; - public UInt32 NumberOfSymbols; - public UInt16 SizeOfOptionalHeader; - public UInt16 Characteristics; - } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER64 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt64 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt64 SizeOfStackReserve; + public UInt64 SizeOfStackCommit; + public UInt64 SizeOfHeapReserve; + public UInt64 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } - // Grabbed the following 2 definitions from http://www.pinvoke.net/default.aspx/Structures/IMAGE_SECTION_HEADER.html - - [StructLayout(LayoutKind.Explicit)] - public struct IMAGE_SECTION_HEADER { - [FieldOffset(0)] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public char[] Name; - [FieldOffset(8)] - public UInt32 VirtualSize; - [FieldOffset(12)] - public UInt32 VirtualAddress; - [FieldOffset(16)] - public UInt32 SizeOfRawData; - [FieldOffset(20)] - public UInt32 PointerToRawData; - [FieldOffset(24)] - public UInt32 PointerToRelocations; - [FieldOffset(28)] - public UInt32 PointerToLinenumbers; - [FieldOffset(32)] - public UInt16 NumberOfRelocations; - [FieldOffset(34)] - public UInt16 NumberOfLinenumbers; - [FieldOffset(36)] - public DataSectionFlags Characteristics; - - public string Section { - get { return new string(Name); } - } - } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_FILE_HEADER + { + public UInt16 Machine; + public UInt16 NumberOfSections; + public UInt32 TimeDateStamp; + public UInt32 PointerToSymbolTable; + public UInt32 NumberOfSymbols; + public UInt16 SizeOfOptionalHeader; + public UInt16 Characteristics; + } - [Flags] - public enum DataSectionFlags : uint { - /// - /// Reserved for future use. - /// - TypeReg = 0x00000000, - /// - /// Reserved for future use. - /// - TypeDsect = 0x00000001, - /// - /// Reserved for future use. - /// - TypeNoLoad = 0x00000002, - /// - /// Reserved for future use. - /// - TypeGroup = 0x00000004, - /// - /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. - /// - TypeNoPadded = 0x00000008, - /// - /// Reserved for future use. - /// - TypeCopy = 0x00000010, - /// - /// The section contains executable code. - /// - ContentCode = 0x00000020, - /// - /// The section contains initialized data. - /// - ContentInitializedData = 0x00000040, - /// - /// The section contains uninitialized data. - /// - ContentUninitializedData = 0x00000080, - /// - /// Reserved for future use. - /// - LinkOther = 0x00000100, - /// - /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. - /// - LinkInfo = 0x00000200, - /// - /// Reserved for future use. - /// - TypeOver = 0x00000400, - /// - /// The section will not become part of the image. This is valid only for object files. - /// - LinkRemove = 0x00000800, - /// - /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. - /// - LinkComDat = 0x00001000, - /// - /// Reset speculative exceptions handling bits in the TLB entries for this section. - /// - NoDeferSpecExceptions = 0x00004000, - /// - /// The section contains data referenced through the global pointer (GP). - /// - RelativeGP = 0x00008000, - /// - /// Reserved for future use. - /// - MemPurgeable = 0x00020000, - /// - /// Reserved for future use. - /// - Memory16Bit = 0x00020000, - /// - /// Reserved for future use. - /// - MemoryLocked = 0x00040000, - /// - /// Reserved for future use. - /// - MemoryPreload = 0x00080000, - /// - /// Align data on a 1-byte boundary. Valid only for object files. - /// - Align1Bytes = 0x00100000, - /// - /// Align data on a 2-byte boundary. Valid only for object files. - /// - Align2Bytes = 0x00200000, - /// - /// Align data on a 4-byte boundary. Valid only for object files. - /// - Align4Bytes = 0x00300000, - /// - /// Align data on an 8-byte boundary. Valid only for object files. - /// - Align8Bytes = 0x00400000, - /// - /// Align data on a 16-byte boundary. Valid only for object files. - /// - Align16Bytes = 0x00500000, - /// - /// Align data on a 32-byte boundary. Valid only for object files. - /// - Align32Bytes = 0x00600000, - /// - /// Align data on a 64-byte boundary. Valid only for object files. - /// - Align64Bytes = 0x00700000, - /// - /// Align data on a 128-byte boundary. Valid only for object files. - /// - Align128Bytes = 0x00800000, - /// - /// Align data on a 256-byte boundary. Valid only for object files. - /// - Align256Bytes = 0x00900000, - /// - /// Align data on a 512-byte boundary. Valid only for object files. - /// - Align512Bytes = 0x00A00000, - /// - /// Align data on a 1024-byte boundary. Valid only for object files. - /// - Align1024Bytes = 0x00B00000, - /// - /// Align data on a 2048-byte boundary. Valid only for object files. - /// - Align2048Bytes = 0x00C00000, - /// - /// Align data on a 4096-byte boundary. Valid only for object files. - /// - Align4096Bytes = 0x00D00000, - /// - /// Align data on an 8192-byte boundary. Valid only for object files. - /// - Align8192Bytes = 0x00E00000, - /// - /// The section contains extended relocations. - /// - LinkExtendedRelocationOverflow = 0x01000000, - /// - /// The section can be discarded as needed. - /// - MemoryDiscardable = 0x02000000, - /// - /// The section cannot be cached. - /// - MemoryNotCached = 0x04000000, - /// - /// The section is not pageable. - /// - MemoryNotPaged = 0x08000000, - /// - /// The section can be shared in memory. - /// - MemoryShared = 0x10000000, - /// - /// The section can be executed as code. - /// - MemoryExecute = 0x20000000, - /// - /// The section can be read. - /// - MemoryRead = 0x40000000, - /// - /// The section can be written to. - /// - MemoryWrite = 0x80000000 - } + // Grabbed the following 2 definitions from http://www.pinvoke.net/default.aspx/Structures/IMAGE_SECTION_HEADER.html - [StructLayout(LayoutKind.Explicit)] - public struct IMAGE_IMPORT_DESCRIPTOR - { - [FieldOffset(0)] - public uint Characteristics; + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_SECTION_HEADER + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] Name; + [FieldOffset(8)] + public UInt32 VirtualSize; + [FieldOffset(12)] + public UInt32 VirtualAddress; + [FieldOffset(16)] + public UInt32 SizeOfRawData; + [FieldOffset(20)] + public UInt32 PointerToRawData; + [FieldOffset(24)] + public UInt32 PointerToRelocations; + [FieldOffset(28)] + public UInt32 PointerToLinenumbers; + [FieldOffset(32)] + public UInt16 NumberOfRelocations; + [FieldOffset(34)] + public UInt16 NumberOfLinenumbers; + [FieldOffset(36)] + public DataSectionFlags Characteristics; + + public string Section + { + get { return new string(Name); } + } + } - [FieldOffset(0)] - public uint OriginalFirstThunk; + [Flags] + public enum DataSectionFlags : uint + { + /// + /// Reserved for future use. + /// + TypeReg = 0x00000000, + /// + /// Reserved for future use. + /// + TypeDsect = 0x00000001, + /// + /// Reserved for future use. + /// + TypeNoLoad = 0x00000002, + /// + /// Reserved for future use. + /// + TypeGroup = 0x00000004, + /// + /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. + /// + TypeNoPadded = 0x00000008, + /// + /// Reserved for future use. + /// + TypeCopy = 0x00000010, + /// + /// The section contains executable code. + /// + ContentCode = 0x00000020, + /// + /// The section contains initialized data. + /// + ContentInitializedData = 0x00000040, + /// + /// The section contains uninitialized data. + /// + ContentUninitializedData = 0x00000080, + /// + /// Reserved for future use. + /// + LinkOther = 0x00000100, + /// + /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. + /// + LinkInfo = 0x00000200, + /// + /// Reserved for future use. + /// + TypeOver = 0x00000400, + /// + /// The section will not become part of the image. This is valid only for object files. + /// + LinkRemove = 0x00000800, + /// + /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. + /// + LinkComDat = 0x00001000, + /// + /// Reset speculative exceptions handling bits in the TLB entries for this section. + /// + NoDeferSpecExceptions = 0x00004000, + /// + /// The section contains data referenced through the global pointer (GP). + /// + RelativeGP = 0x00008000, + /// + /// Reserved for future use. + /// + MemPurgeable = 0x00020000, + /// + /// Reserved for future use. + /// + Memory16Bit = 0x00020000, + /// + /// Reserved for future use. + /// + MemoryLocked = 0x00040000, + /// + /// Reserved for future use. + /// + MemoryPreload = 0x00080000, + /// + /// Align data on a 1-byte boundary. Valid only for object files. + /// + Align1Bytes = 0x00100000, + /// + /// Align data on a 2-byte boundary. Valid only for object files. + /// + Align2Bytes = 0x00200000, + /// + /// Align data on a 4-byte boundary. Valid only for object files. + /// + Align4Bytes = 0x00300000, + /// + /// Align data on an 8-byte boundary. Valid only for object files. + /// + Align8Bytes = 0x00400000, + /// + /// Align data on a 16-byte boundary. Valid only for object files. + /// + Align16Bytes = 0x00500000, + /// + /// Align data on a 32-byte boundary. Valid only for object files. + /// + Align32Bytes = 0x00600000, + /// + /// Align data on a 64-byte boundary. Valid only for object files. + /// + Align64Bytes = 0x00700000, + /// + /// Align data on a 128-byte boundary. Valid only for object files. + /// + Align128Bytes = 0x00800000, + /// + /// Align data on a 256-byte boundary. Valid only for object files. + /// + Align256Bytes = 0x00900000, + /// + /// Align data on a 512-byte boundary. Valid only for object files. + /// + Align512Bytes = 0x00A00000, + /// + /// Align data on a 1024-byte boundary. Valid only for object files. + /// + Align1024Bytes = 0x00B00000, + /// + /// Align data on a 2048-byte boundary. Valid only for object files. + /// + Align2048Bytes = 0x00C00000, + /// + /// Align data on a 4096-byte boundary. Valid only for object files. + /// + Align4096Bytes = 0x00D00000, + /// + /// Align data on an 8192-byte boundary. Valid only for object files. + /// + Align8192Bytes = 0x00E00000, + /// + /// The section contains extended relocations. + /// + LinkExtendedRelocationOverflow = 0x01000000, + /// + /// The section can be discarded as needed. + /// + MemoryDiscardable = 0x02000000, + /// + /// The section cannot be cached. + /// + MemoryNotCached = 0x04000000, + /// + /// The section is not pageable. + /// + MemoryNotPaged = 0x08000000, + /// + /// The section can be shared in memory. + /// + MemoryShared = 0x10000000, + /// + /// The section can be executed as code. + /// + MemoryExecute = 0x20000000, + /// + /// The section can be read. + /// + MemoryRead = 0x40000000, + /// + /// The section can be written to. + /// + MemoryWrite = 0x80000000 + } - [FieldOffset(4)] - public uint TimeDateStamp; + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_IMPORT_DESCRIPTOR + { + [FieldOffset(0)] + public uint Characteristics; - [FieldOffset(8)] - public uint ForwarderChain; + [FieldOffset(0)] + public uint OriginalFirstThunk; - [FieldOffset(12)] - public uint Name; + [FieldOffset(4)] + public uint TimeDateStamp; - [FieldOffset(16)] - public uint FirstThunk; - } + [FieldOffset(8)] + public uint ForwarderChain; - [StructLayout(LayoutKind.Explicit)] - public struct IMAGE_THUNK_DATA - { - [FieldOffset(0)] - public IntPtr ForwarderString; + [FieldOffset(12)] + public uint Name; - [FieldOffset(0)] - public IntPtr Function; + [FieldOffset(16)] + public uint FirstThunk; + } - [FieldOffset(0)] - public IntPtr Ordinal; + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_THUNK_DATA + { + [FieldOffset(0)] + public IntPtr ForwarderString; - [FieldOffset(0)] - public IntPtr AddressOfData; - } + [FieldOffset(0)] + public IntPtr Function; - public unsafe struct HookData - { - public IMAGE_THUNK_DATA* thunk; - public IntPtr originalFunction; + [FieldOffset(0)] + public IntPtr Ordinal; - public HookData(IMAGE_THUNK_DATA* thunk) - { - this.thunk = thunk; - originalFunction = thunk->Function; + [FieldOffset(0)] + public IntPtr AddressOfData; } - } - #endregion File Header Structures - - #region Private Fields + public unsafe struct HookData + { + public IMAGE_THUNK_DATA* thunk; + public IntPtr originalFunction; - private IntPtr imageBase; + public HookData(IMAGE_THUNK_DATA* thunk) + { + this.thunk = thunk; + originalFunction = thunk->Function; + } + } - /// - /// The DOS header - /// - private IMAGE_DOS_HEADER dosHeader; - /// - /// The file header - /// - private IMAGE_FILE_HEADER fileHeader; - /// - /// Optional 32 bit file header - /// - private IMAGE_OPTIONAL_HEADER32 optionalHeader32; - /// - /// Optional 64 bit file header - /// - private IMAGE_OPTIONAL_HEADER64 optionalHeader64; - /// - /// Image Section headers. Number of sections is in the file header. - /// - private IMAGE_SECTION_HEADER[] imageSectionHeaders; + #endregion File Header Structures - private Dictionary, HookData> hookThunks = new Dictionary, HookData>(); + #region Private Fields - #endregion Private Fields + private IntPtr imageBase; - #region Public Methods + /// + /// The DOS header + /// + private IMAGE_DOS_HEADER dosHeader; + /// + /// The file header + /// + private IMAGE_FILE_HEADER fileHeader; + /// + /// Optional 32 bit file header + /// + private IMAGE_OPTIONAL_HEADER32 optionalHeader32; + /// + /// Optional 64 bit file header + /// + private IMAGE_OPTIONAL_HEADER64 optionalHeader64; + /// + /// Image Section headers. Number of sections is in the file header. + /// + private IMAGE_SECTION_HEADER[] imageSectionHeaders; - public unsafe AssemblyIATHooker(IntPtr imageBase) - { - this.imageBase = imageBase; - var stream = new UnmanagedMemoryStream((byte*)imageBase, 5000, 5000, FileAccess.ReadWrite); - Create(stream); - } + private Dictionary, HookData> hookThunks = new Dictionary, HookData>(); - private void Create(Stream stream) - { - BinaryReader reader = new BinaryReader(stream); - dosHeader = FromBinaryReader(reader); + #endregion Private Fields - // Add 4 bytes to the offset - stream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin); + #region Public Methods - UInt32 ntHeadersSignature = reader.ReadUInt32(); - fileHeader = FromBinaryReader(reader); - if (Is32BitHeader) - { - optionalHeader32 = FromBinaryReader(reader); - } - else + public unsafe AssemblyIATHooker(IntPtr imageBase) { - optionalHeader64 = FromBinaryReader(reader); + this.imageBase = imageBase; + var stream = new UnmanagedMemoryStream((byte*)imageBase, 5000, 5000, FileAccess.ReadWrite); + Create(stream); } - imageSectionHeaders = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections]; - for (int headerNo = 0; headerNo < imageSectionHeaders.Length; ++headerNo) + private void Create(Stream stream) { - imageSectionHeaders[headerNo] = FromBinaryReader(reader); + BinaryReader reader = new BinaryReader(stream); + dosHeader = FromBinaryReader(reader); + + // Add 4 bytes to the offset + stream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin); + + UInt32 ntHeadersSignature = reader.ReadUInt32(); + fileHeader = FromBinaryReader(reader); + if (Is32BitHeader) + { + optionalHeader32 = FromBinaryReader(reader); + } + else + { + optionalHeader64 = FromBinaryReader(reader); + } + + imageSectionHeaders = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections]; + for (int headerNo = 0; headerNo < imageSectionHeaders.Length; ++headerNo) + { + imageSectionHeaders[headerNo] = FromBinaryReader(reader); + } } - } - /// - /// Reads in a block from a file and converts it to the struct - /// type specified by the template parameter - /// - /// - /// - /// - public static T FromBinaryReader(BinaryReader reader) { - // Read in a byte array - byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); - - // Pin the managed memory while, copy it out the data, then unpin it - GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); - T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); - handle.Free(); - - return theStructure; - } + /// + /// Reads in a block from a file and converts it to the struct + /// type specified by the template parameter + /// + /// + /// + /// + public static T FromBinaryReader(BinaryReader reader) + { + // Read in a byte array + byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); - /// - /// Gets if the file header is 32 bit or not - /// - public bool Is32BitHeader { - get { - UInt16 IMAGE_FILE_32BIT_MACHINE = 0x0100; - return (IMAGE_FILE_32BIT_MACHINE & FileHeader.Characteristics) == IMAGE_FILE_32BIT_MACHINE; - } - } + // Pin the managed memory while, copy it out the data, then unpin it + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + handle.Free(); - /// - /// Gets the file header - /// - public IMAGE_FILE_HEADER FileHeader { - get { - return fileHeader; - } - } + return theStructure; + } - /// - /// Gets the optional header - /// - public IMAGE_OPTIONAL_HEADER32 OptionalHeader32 { - get { - return optionalHeader32; - } - } + /// + /// Gets if the file header is 32 bit or not + /// + public bool Is32BitHeader + { + get + { + UInt16 IMAGE_FILE_32BIT_MACHINE = 0x0100; + return (IMAGE_FILE_32BIT_MACHINE & FileHeader.Characteristics) == IMAGE_FILE_32BIT_MACHINE; + } + } - /// - /// Gets the optional header - /// - public IMAGE_OPTIONAL_HEADER64 OptionalHeader64 { - get { - return optionalHeader64; - } - } + /// + /// Gets the file header + /// + public IMAGE_FILE_HEADER FileHeader + { + get + { + return fileHeader; + } + } - public IMAGE_SECTION_HEADER[] ImageSectionHeaders { - get { - return imageSectionHeaders; - } - } + /// + /// Gets the optional header + /// + public IMAGE_OPTIONAL_HEADER32 OptionalHeader32 + { + get + { + return optionalHeader32; + } + } - public IMAGE_DATA_DIRECTORY ImportsDirectory - { - get + /// + /// Gets the optional header + /// + public IMAGE_OPTIONAL_HEADER64 OptionalHeader64 { - if (Is32BitHeader) + get { - return optionalHeader32.ImportTable; + return optionalHeader64; } + } - return optionalHeader64.ImportTable; + public IMAGE_SECTION_HEADER[] ImageSectionHeaders + { + get + { + return imageSectionHeaders; + } } - } - public enum ProtectMode - { - PAGE_NOACCESS = 0x1, - PAGE_READONLY = 0x2, - PAGE_READWRITE = 0x4, - PAGE_WRITECOPY = 0x8, - PAGE_EXECUTE = 0x10, - PAGE_EXECUTE_READ = 0x20, - PAGE_EXECUTE_READWRITE = 0x40, - PAGE_EXECUTE_WRITECOPY = 0x80 - } + public IMAGE_DATA_DIRECTORY ImportsDirectory + { + get + { + if (Is32BitHeader) + { + return optionalHeader32.ImportTable; + } + + return optionalHeader64.ImportTable; + } + } - [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] - private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); + public enum ProtectMode + { + PAGE_NOACCESS = 0x1, + PAGE_READONLY = 0x2, + PAGE_READWRITE = 0x4, + PAGE_WRITECOPY = 0x8, + PAGE_EXECUTE = 0x10, + PAGE_EXECUTE_READ = 0x20, + PAGE_EXECUTE_READWRITE = 0x40, + PAGE_EXECUTE_WRITECOPY = 0x80 + } - private static bool VirtualProtect(IntPtr lpAdress, int dwSize, ProtectMode flNewProtect, out ProtectMode lpflOldProtect) - { - bool result = VirtualProtect(lpAdress, (IntPtr)dwSize, (uint)flNewProtect, out uint oldProtect); - lpflOldProtect = (ProtectMode)oldProtect; - return result; - } + [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] + private static extern bool VirtualProtect(IntPtr lpAddress, IntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); - public unsafe delegate void ApplyAction(IMAGE_THUNK_DATA* thunk); + private static bool VirtualProtect(IntPtr lpAdress, int dwSize, ProtectMode flNewProtect, out ProtectMode lpflOldProtect) + { + bool result = VirtualProtect(lpAdress, (IntPtr)dwSize, (uint)flNewProtect, out uint oldProtect); + lpflOldProtect = (ProtectMode)oldProtect; + return result; + } - public unsafe void CreateIATHook(string targetLibrary, string targetFunction, ApplyAction onFound) - { - var importDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(imageBase + (int)ImportsDirectory.VirtualAddress); + public unsafe delegate void ApplyAction(IMAGE_THUNK_DATA* thunk); - while (importDescriptor->Name != 0) + public unsafe void CreateIATHook(string targetLibrary, string targetFunction, ApplyAction onFound) { - var libraryNamePtr = imageBase + (int)importDescriptor->Name; - var libraryName = Marshal.PtrToStringAnsi(libraryNamePtr); + var importDescriptor = (IMAGE_IMPORT_DESCRIPTOR*)(imageBase + (int)ImportsDirectory.VirtualAddress); - if (libraryName.Equals(targetLibrary)) + while (importDescriptor->Name != 0) { - var originalFirstThunk = (IMAGE_THUNK_DATA*)(imageBase + (int)importDescriptor->OriginalFirstThunk); - var firstThunk = (IMAGE_THUNK_DATA*)(imageBase + (int)importDescriptor->FirstThunk); + var libraryNamePtr = imageBase + (int)importDescriptor->Name; + var libraryName = Marshal.PtrToStringAnsi(libraryNamePtr); - while (originalFirstThunk->AddressOfData != IntPtr.Zero) + if (libraryName.Equals(targetLibrary)) { - var functionNamePtr = (imageBase + (int)originalFirstThunk->AddressOfData); - var functionName = Marshal.PtrToStringAnsi(functionNamePtr + 2); + var originalFirstThunk = (IMAGE_THUNK_DATA*)(imageBase + (int)importDescriptor->OriginalFirstThunk); + var firstThunk = (IMAGE_THUNK_DATA*)(imageBase + (int)importDescriptor->FirstThunk); - if (functionName.Equals(targetFunction)) + while (originalFirstThunk->AddressOfData != IntPtr.Zero) { - IntPtr address = (IntPtr)(&firstThunk->Function); - VirtualProtect(address, 8, ProtectMode.PAGE_READWRITE, out ProtectMode oldProtect); - var key = new Tuple(targetLibrary, targetFunction); - - if (!hookThunks.ContainsKey(key)) - hookThunks.Add(key, new HookData(firstThunk)); - onFound.Invoke(firstThunk); - break; + var functionNamePtr = (imageBase + (int)originalFirstThunk->AddressOfData); + var functionName = Marshal.PtrToStringAnsi(functionNamePtr + 2); + + if (functionName.Equals(targetFunction)) + { + IntPtr address = (IntPtr)(&firstThunk->Function); + VirtualProtect(address, 8, ProtectMode.PAGE_READWRITE, out ProtectMode oldProtect); + var key = new Tuple(targetLibrary, targetFunction); + + if (!hookThunks.ContainsKey(key)) + hookThunks.Add(key, new HookData(firstThunk)); + onFound.Invoke(firstThunk); + break; + } + ++originalFirstThunk; + ++firstThunk; } - ++originalFirstThunk; - ++firstThunk; + break; } - break; - } - importDescriptor++; + importDescriptor++; + } } - } - public unsafe void UnpatchIATHook(string targetLibrary, string targetFunction) - { - var key = new Tuple(targetLibrary, targetFunction); - if (hookThunks.ContainsKey(key)) + public unsafe void UnpatchIATHook(string targetLibrary, string targetFunction) { - HookData hook = hookThunks[key]; - hook.thunk->Function = hook.originalFunction; - hookThunks.Remove(key); + var key = new Tuple(targetLibrary, targetFunction); + if (hookThunks.ContainsKey(key)) + { + HookData hook = hookThunks[key]; + hook.thunk->Function = hook.originalFunction; + hookThunks.Remove(key); + } } - } - #endregion Properties - } + #endregion Properties + } } diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs index abd8ad96..21d6243b 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs @@ -23,7 +23,6 @@ internal unsafe class Assembly_Load_Hook : Hook Date: Sat, 10 Jun 2023 10:00:20 +0300 Subject: [PATCH 10/13] Cache assembly il2cpp_domain_get_assemblies return list --- .../API_il2cpp_domain_get_assemblies_hook.cs | 54 ++++++++++--------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs index 4807518d..09b506d9 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs @@ -12,12 +12,8 @@ internal unsafe class API_il2cpp_domain_get_assemblies_hook : Hook 0) { - Il2CppAssembly** oldArray = (Il2CppAssembly**)assemblyArrayPtr; - int origSize = (int)*size; + CreateCustomAssemblyList((int)*size, assemblyArrayPtr); + + *size = lastAssemblyListSize; + return currentDataPtr; + } + + return assemblyArrayPtr; + } - int newSize = origSize + InjectorHelpers.InjectedImages.Count; - if (currentDataPtr != IntPtr.Zero) - Marshal.FreeHGlobal(currentDataPtr); + private void CreateCustomAssemblyList(int origSize, IntPtr assemblyArrayPtr) + { + var newSize = origSize + InjectorHelpers.InjectedImages.Count; + if (lastAssemblyListSize == newSize) return; - currentDataPtr = Marshal.AllocHGlobal(newSize * sizeof(Il2CppSystem.IntPtr)); - Il2CppAssembly** newArray = (Il2CppAssembly**)currentDataPtr; + Il2CppAssembly** oldArray = (Il2CppAssembly**)assemblyArrayPtr; - int i; + if (currentDataPtr != IntPtr.Zero) + Marshal.FreeHGlobal(currentDataPtr); - for (i = 0; i < origSize; i++) - newArray[i] = oldArray[i]; + currentDataPtr = Marshal.AllocHGlobal(newSize * sizeof(Il2CppSystem.IntPtr)); + Il2CppAssembly** newArray = (Il2CppAssembly**)currentDataPtr; - i = origSize; - foreach (IntPtr imagePtr in InjectorHelpers.InjectedImages.Values) - { - var image = UnityVersionHandler.Wrap((Il2CppImage*)imagePtr); - newArray[i] = image.Assembly; - i++; - } + int i; - *size = newSize; - return currentDataPtr; + for (i = 0; i < origSize; i++) + newArray[i] = oldArray[i]; + + i = origSize; + foreach (IntPtr imagePtr in InjectorHelpers.InjectedImages.Values) + { + var image = UnityVersionHandler.Wrap((Il2CppImage*)imagePtr); + newArray[i] = image.Assembly; + i++; } - return assemblyArrayPtr; + lastAssemblyListSize = newSize; } public override IntPtr FindTargetMethod() From b33cd6d500c54cff643a2c8854324d002b60e856 Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Sat, 10 Jun 2023 10:05:02 +0300 Subject: [PATCH 11/13] Move assembly image creation time back --- .../Injection/InjectorHelpers.cs | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs index aacfb0c4..f123734e 100644 --- a/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs +++ b/Il2CppInterop.Runtime/Injection/InjectorHelpers.cs @@ -167,19 +167,9 @@ private static int GetFileAttributesExDetour(IntPtr lpFileName, int fInfoLevelId var assemblyList = new AssemblyListFile(filePath); - CreateDefaultInjectedAssembly(); - assemblyList.AddAssembly(InjectedMonoTypesAssemblyName); - var allFiles = AssemblyInjectorComponent.ModAssemblies; - - foreach (var file in allFiles) + foreach (var assemblyName in InjectedImages.Keys) { - var extension = Path.GetExtension(file); - if (extension.Equals(".dll")) - { - var assemblyName = Path.GetFileName(file); - CreateInjectedImage(assemblyName); - assemblyList.AddAssembly(assemblyName); - } + assemblyList.AddAssembly(assemblyName); } NewAssemblyListFile = assemblyList.GetTmpFile(); @@ -228,6 +218,19 @@ private static void UnpatchIATHooks() // Setup before unity loads assembly list internal static void EarlySetup() { + CreateDefaultInjectedAssembly(); + var allFiles = AssemblyInjectorComponent.ModAssemblies; + + foreach (var file in allFiles) + { + var extension = Path.GetExtension(file); + if (extension.Equals(".dll")) + { + var assemblyName = Path.GetFileName(file); + CreateInjectedImage(assemblyName); + } + } + UnityPlayerIATHooker = new AssemblyIATHooker(UnityPlayerHandle); UnityPlayerIATHooker.CreateIATHook("KERNEL32.dll", "ReadFile", thunk => { From 8784088cd7818143bbe85c824ca02218e2fa427f Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Sat, 10 Jun 2023 10:06:27 +0300 Subject: [PATCH 12/13] Testing change --- .../Startup/Il2CppInteropRuntime.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs index 5f9379a0..e600b8ff 100644 --- a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs +++ b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs @@ -40,8 +40,20 @@ public static Il2CppInteropRuntime Create(RuntimeConfiguration configuration) return res; } + public class FakeListProvider : IAssemblyListProvider + { + public IEnumerable GetAssemblyList() + { + var coreFolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var pluginsFolder = Path.Combine(coreFolder, "../plugins/"); + + return Directory.EnumerateFiles(pluginsFolder, "*", SearchOption.AllDirectories); + } + } + public override void Start() { + this.AddAssemblyInjector(); UnityVersionHandler.RecalculateHandlers(); base.Start(); } From af8fb61dc02bfca35073ee94c8dfcc9546ea4f34 Mon Sep 17 00:00:00 2001 From: kremnev8 Date: Wed, 14 Jun 2023 13:36:26 +0300 Subject: [PATCH 13/13] Unity 2019 fix WIP --- .../GameManagersAssemblyListFile.cs | 143 ++++++++++++++++++ .../AssemblyList/IAssemblyListFile.cs | 10 ++ .../JSONAssemblyListFile.cs} | 19 ++- .../Injection/Hooks/Assembly_Load_Hook.cs | 4 +- .../Injection/InjectorHelpers.cs | 34 +++-- .../Runtime/UnityVersionHandler.cs | 12 ++ 6 files changed, 199 insertions(+), 23 deletions(-) create mode 100644 Il2CppInterop.Runtime/Injection/AssemblyList/GameManagersAssemblyListFile.cs create mode 100644 Il2CppInterop.Runtime/Injection/AssemblyList/IAssemblyListFile.cs rename Il2CppInterop.Runtime/Injection/{AssemblyListFile.cs => AssemblyList/JSONAssemblyListFile.cs} (61%) diff --git a/Il2CppInterop.Runtime/Injection/AssemblyList/GameManagersAssemblyListFile.cs b/Il2CppInterop.Runtime/Injection/AssemblyList/GameManagersAssemblyListFile.cs new file mode 100644 index 00000000..b18c5e38 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/AssemblyList/GameManagersAssemblyListFile.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Il2CppInterop.Runtime.Injection +{ + internal class GameManagersAssemblyListFile : IAssemblyListFile + { + private List _assemblies = new List(); + private string originalFile; + private string newFile; + + public bool IsTargetFile(string originalFilePath) + { + return originalFilePath.Contains("globalgamemanagers"); + } + + public void Setup(string originalFilePath) + { + if (originalFile != null) return; + originalFile = originalFilePath; + } + + public void AddAssembly(string name) + { + _assemblies.Add(name); + } + + public string GetOrCreateNewFile() + { + if (newFile != null) return newFile; + + newFile = Path.GetTempFileName(); + CreateModifiedFile(); + return newFile; + } + + private void CreateModifiedFile() + { + using var outputStream = File.Open(newFile, FileMode.Create); + using var outputWriter = new BinaryWriter(outputStream, Encoding.ASCII, false); + using var inputStream = File.Open(originalFile, FileMode.Open); + using var inputReader = new BinaryReader(inputStream, Encoding.ASCII, false); + + // Assembly list always starts with UnityEngine.dll + var startPos = SeekFirstName(inputStream, inputReader); + if (startPos == -1) + { + throw new Exception("Failed to find start of assembly list in globalgamemanagers file!"); + } + + inputStream.Position = 0; + startPos -= 8; + + for (var i = 0; i < startPos; i++) + { + outputWriter.Write(inputReader.ReadByte()); + } + + var assemblyCount = inputReader.ReadInt32(); + List newAssemblyList = new List(assemblyCount + _assemblies.Count); + List newAssemblyTypes = new List(assemblyCount + _assemblies.Count); + for (var i = 0; i < assemblyCount; i++) + { + newAssemblyList.Add(ReadString(inputReader)); + } + + assemblyCount = inputReader.ReadInt32(); + for (var i = 0; i < assemblyCount; i++) + { + newAssemblyTypes.Add(inputReader.ReadInt32()); + } + + newAssemblyList.AddRange(_assemblies); + newAssemblyTypes.AddRange(_assemblies.Select(_ => 16)); + + outputWriter.Write(newAssemblyList.Count); + foreach (var assemblyName in newAssemblyList) + { + WriteString(outputWriter, assemblyName); + } + + outputWriter.Write(newAssemblyTypes.Count); + foreach (var assemblyType in newAssemblyTypes) + { + outputWriter.Write(assemblyType); + } + + while (inputStream.Position < inputStream.Length) + { + outputWriter.Write(inputReader.ReadByte()); + } + } + + private static void WriteString(BinaryWriter outputWriter, string @string) + { + outputWriter.Write(@string.Length); + var paddedLenth = (int)(Math.Ceiling(@string.Length / 4f) * 4f); + for (int i = 0; i < paddedLenth; i++) + { + if (i < @string.Length) + outputWriter.Write(@string[i]); + else + outputWriter.Write((byte)0); + } + } + + private static string ReadString(BinaryReader inputReader) + { + var length = inputReader.ReadInt32(); + var paddedLenth = (int)(Math.Ceiling(length / 4f) * 4f); + StringBuilder sb = new StringBuilder(length); + for (var j = 0; j < paddedLenth; j++) + { + var c = inputReader.ReadChar(); + if (j < length) + sb.Append(c); + } + + return sb.ToString(); + } + + private static long SeekFirstName(FileStream inputStream, BinaryReader inputReader) + { + while (inputStream.Position < inputStream.Length) + { + var currentPos = inputStream.Position; + var firstChar = inputReader.ReadChar(); + if (firstChar != 'U') continue; + + var nextString = new string(inputReader.ReadChars(14)); + if (!nextString.Equals("nityEngine.dll")) continue; + + return currentPos; + } + + return -1; + } + } +} diff --git a/Il2CppInterop.Runtime/Injection/AssemblyList/IAssemblyListFile.cs b/Il2CppInterop.Runtime/Injection/AssemblyList/IAssemblyListFile.cs new file mode 100644 index 00000000..d46251c0 --- /dev/null +++ b/Il2CppInterop.Runtime/Injection/AssemblyList/IAssemblyListFile.cs @@ -0,0 +1,10 @@ +namespace Il2CppInterop.Runtime.Injection +{ + internal interface IAssemblyListFile + { + public bool IsTargetFile(string originalFilePath); + public void Setup(string originalFilePath); + public void AddAssembly(string name); + public string GetOrCreateNewFile(); + } +} diff --git a/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs b/Il2CppInterop.Runtime/Injection/AssemblyList/JSONAssemblyListFile.cs similarity index 61% rename from Il2CppInterop.Runtime/Injection/AssemblyListFile.cs rename to Il2CppInterop.Runtime/Injection/AssemblyList/JSONAssemblyListFile.cs index 39086ece..9485e23d 100644 --- a/Il2CppInterop.Runtime/Injection/AssemblyListFile.cs +++ b/Il2CppInterop.Runtime/Injection/AssemblyList/JSONAssemblyListFile.cs @@ -4,28 +4,35 @@ namespace Il2CppInterop.Runtime.Injection { - public class AssemblyListFile + internal class JSONAssemblyListFile : IAssemblyListFile { - private readonly JsonNode node; - private readonly JsonArray names; - private readonly JsonArray types; + private JsonNode node; + private JsonArray names; + private JsonArray types; private string newFile; - public AssemblyListFile(string originalFilePath) + public void Setup(string originalFilePath) { + if (node != null) return; + node = JsonNode.Parse(File.ReadAllText(originalFilePath)); names = node["names"].AsArray(); types = node["types"].AsArray(); } + public bool IsTargetFile(string originalFilePath) + { + return originalFilePath.Contains("ScriptingAssemblies.json"); + } + public void AddAssembly(string name) { names.Add(name); types.Add(16); } - public string GetTmpFile() + public string GetOrCreateNewFile() { if (!string.IsNullOrEmpty(newFile)) return newFile; diff --git a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs index 21d6243b..0c2e1fec 100644 --- a/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs +++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs @@ -19,10 +19,12 @@ internal unsafe class Assembly_Load_Hook : Hook StIndOpcodes = new() { @@ -124,8 +124,8 @@ internal static void Setup() FromNameHook.ApplyHook(); RunFinalizerPatch.ApplyHook(); - AssemblyGetLoadedAssemblyHook.ApplyHook(); - AppDomainGetAssembliesHook.ApplyHook(); + //AssemblyGetLoadedAssemblyHook.ApplyHook(); + //AppDomainGetAssembliesHook.ApplyHook(); } [StructLayout(LayoutKind.Sequential)] @@ -160,21 +160,19 @@ public static extern IntPtr CreateFile( private static int GetFileAttributesExDetour(IntPtr lpFileName, int fInfoLevelId, IntPtr lpFileInformation) { var filePath = Marshal.PtrToStringUni(lpFileName); + filePath = filePath.Replace(@"\\?\", ""); - if (filePath.Contains("ScriptingAssemblies.json")) + if (AssemblyListFile.IsTargetFile(filePath)) { - filePath = filePath.Replace(@"\\?\", ""); - - var assemblyList = new AssemblyListFile(filePath); - + AssemblyListFile.Setup(filePath); foreach (var assemblyName in InjectedImages.Keys) { - assemblyList.AddAssembly(assemblyName); + AssemblyListFile.AddAssembly(assemblyName); } - NewAssemblyListFile = assemblyList.GetTmpFile(); - Logger.Instance.LogInformation($"Forcing unity to read assembly list from {NewAssemblyListFile}"); - var newlpFileName = Marshal.StringToHGlobalUni(NewAssemblyListFile); + var newFile = AssemblyListFile.GetOrCreateNewFile(); + Logger.Instance.LogInformation($"Forcing unity to read assembly list from {newFile}"); + var newlpFileName = Marshal.StringToHGlobalUni(newFile); var result = GetFileAttributesEx(newlpFileName, fInfoLevelId, lpFileInformation); Marshal.FreeHGlobal(newlpFileName); @@ -197,10 +195,9 @@ private static int ReadFileDetour(IntPtr handle, IntPtr bytes, uint numBytesToRe if (res != 0) { var filePath = sb.ToString(); - if (filePath.Contains("ScriptingAssemblies.json")) + if (AssemblyListFile.IsTargetFile(filePath)) { - IntPtr newHandle = CreateFile(NewAssemblyListFile, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); - UnpatchIATHooks(); + IntPtr newHandle = CreateFile(AssemblyListFile.GetOrCreateNewFile(), FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero); return ReadFile(newHandle, bytes, numBytesToRead, numBytesRead, overlapped); } } @@ -208,11 +205,14 @@ private static int ReadFileDetour(IntPtr handle, IntPtr bytes, uint numBytesToRe return ReadFile(handle, bytes, numBytesToRead, numBytesRead, overlapped); } - private static void UnpatchIATHooks() + internal static void UnpatchIATHooks() { + if (UnityPlayerIATHooker == null) return; + Logger.Instance.LogInformation("Unpatching UnityPlayer IAT hooks"); UnityPlayerIATHooker.UnpatchIATHook("KERNEL32.dll", "ReadFile"); UnityPlayerIATHooker.UnpatchIATHook("KERNEL32.dll", "GetFileAttributesExW"); + UnityPlayerIATHooker = null; } // Setup before unity loads assembly list @@ -231,6 +231,8 @@ internal static void EarlySetup() } } + AssemblyListFile = UnityVersionHandler.GetAssemblyListFile(); + UnityPlayerIATHooker = new AssemblyIATHooker(UnityPlayerHandle); UnityPlayerIATHooker.CreateIATHook("KERNEL32.dll", "ReadFile", thunk => { diff --git a/Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs b/Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs index 0e4e1f7d..d7e8c2a8 100644 --- a/Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs +++ b/Il2CppInterop.Runtime/Runtime/UnityVersionHandler.cs @@ -4,6 +4,7 @@ using System.Reflection; using Il2CppInterop.Common; using Il2CppInterop.Common.Extensions; +using Il2CppInterop.Runtime.Injection; using Il2CppInterop.Runtime.Runtime.VersionSpecific.Assembly; using Il2CppInterop.Runtime.Runtime.VersionSpecific.AssemblyName; using Il2CppInterop.Runtime.Runtime.VersionSpecific.Class; @@ -79,6 +80,8 @@ static UnityVersionHandler() public static bool HasShimForGetMethod { get; private set; } public static bool IsMetadataV29OrHigher { get; private set; } + public static bool HasScriptAssembliesFile { get; private set; } + // Version since which extra_arg is set to invoke_multicast, necessitating constructor calls public static bool MustUseDelegateConstructor => IsMetadataV29OrHigher; @@ -98,6 +101,7 @@ internal static void RecalculateHandlers() HasGetMethodFromReflection = unityVersion > new Version(2018, 1, 0); IsMetadataV29OrHigher = unityVersion >= new Version(2021, 2, 0); + HasScriptAssembliesFile = unityVersion >= new Version(2020, 0, 0); HasShimForGetMethod = unityVersion >= new Version(2020, 3, 41) || IsMetadataV29OrHigher; @@ -129,6 +133,14 @@ private static Type[] GetAllTypesSafe() return typeof(UnityVersionHandler).Assembly.GetTypesSafe(); } + internal static IAssemblyListFile GetAssemblyListFile() + { + if (HasScriptAssembliesFile) + return new JSONAssemblyListFile(); + + return new GameManagersAssemblyListFile(); + } + //Assemblies public static INativeAssemblyStruct NewAssembly() {