diff --git a/Il2CppInterop.Common/AssemblyIATHooker.cs b/Il2CppInterop.Common/AssemblyIATHooker.cs
new file mode 100644
index 00000000..93b8f0dc
--- /dev/null
+++ b/Il2CppInterop.Common/AssemblyIATHooker.cs
@@ -0,0 +1,660 @@
+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/IL2CPP.cs b/Il2CppInterop.Runtime/IL2CPP.cs
index 7604d9c0..04449ad5 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,24 +36,24 @@ 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];
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;
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/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/AssemblyList/JSONAssemblyListFile.cs b/Il2CppInterop.Runtime/Injection/AssemblyList/JSONAssemblyListFile.cs
new file mode 100644
index 00000000..9485e23d
--- /dev/null
+++ b/Il2CppInterop.Runtime/Injection/AssemblyList/JSONAssemblyListFile.cs
@@ -0,0 +1,46 @@
+using System;
+using System.IO;
+using System.Text.Json.Nodes;
+
+namespace Il2CppInterop.Runtime.Injection
+{
+ internal class JSONAssemblyListFile : IAssemblyListFile
+ {
+ private JsonNode node;
+ private JsonArray names;
+ private JsonArray types;
+
+ private string newFile;
+
+ 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 GetOrCreateNewFile()
+ {
+ if (!string.IsNullOrEmpty(newFile)) return newFile;
+
+ var newJson = node.ToJsonString();
+ newFile = Path.GetTempFileName();
+
+ File.WriteAllText(newFile, newJson);
+ return newFile;
+ }
+ }
+}
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..d513ef30 100644
--- a/Il2CppInterop.Runtime/Injection/EnumInjector.cs
+++ b/Il2CppInterop.Runtime/Injection/EnumInjector.cs
@@ -206,7 +206,13 @@ 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;
+
+ 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/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..09b506d9
--- /dev/null
+++ b/Il2CppInterop.Runtime/Injection/Hooks/API_il2cpp_domain_get_assemblies_hook.cs
@@ -0,0 +1,67 @@
+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);
+
+ private IntPtr currentDataPtr = IntPtr.Zero;
+ private int lastAssemblyListSize = -1;
+
+ private IntPtr Hook(IntPtr domain, long* size)
+ {
+ IntPtr assemblyArrayPtr = Original(domain, size);
+
+ if (InjectorHelpers.InjectedImages.Count > 0)
+ {
+ CreateCustomAssemblyList((int)*size, assemblyArrayPtr);
+
+ *size = lastAssemblyListSize;
+ return currentDataPtr;
+ }
+
+ return assemblyArrayPtr;
+ }
+
+ private void CreateCustomAssemblyList(int origSize, IntPtr assemblyArrayPtr)
+ {
+ var newSize = origSize + InjectorHelpers.InjectedImages.Count;
+ if (lastAssemblyListSize == newSize) return;
+
+ Il2CppAssembly** oldArray = (Il2CppAssembly**)assemblyArrayPtr;
+
+ 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++;
+ }
+
+ lastAssemblyListSize = newSize;
+ }
+
+ 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
new file mode 100644
index 00000000..8ae7fc13
--- /dev/null
+++ b/Il2CppInterop.Runtime/Injection/Hooks/AppDomain_GetAssemblies_Hook.cs
@@ -0,0 +1,75 @@
+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 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..246a9255
--- /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)
+ {
+ var assemblyName = Marshal.PtrToStringAnsi(name);
+ Il2CppAssembly* assembly = Original(name);
+
+ if (assembly == null)
+ {
+ if (InjectorHelpers.TryGetInjectedImage(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..0c2e1fec
--- /dev/null
+++ b/Il2CppInterop.Runtime/Injection/Hooks/Assembly_Load_Hook.cs
@@ -0,0 +1,47 @@
+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);
+ InjectorHelpers.UnpatchIATHooks();
+ var assemblyName = Marshal.PtrToStringAnsi(name);
+
+ Logger.Instance.LogInformation($"Assembly::Load {assemblyName}");
+ if (assembly == null)
+ {
+ if (InjectorHelpers.TryGetInjectedImage(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/Hooks/Class_FromName_Hook.cs b/Il2CppInterop.Runtime/Injection/Hooks/Class_FromName_Hook.cs
index 09431c93..d87c75bf 100644
--- a/Il2CppInterop.Runtime/Injection/Hooks/Class_FromName_Hook.cs
+++ b/Il2CppInterop.Runtime/Injection/Hooks/Class_FromName_Hook.cs
@@ -24,6 +24,7 @@ internal unsafe class Class_FromName_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 IAssemblyListFile AssemblyListFile;
internal static readonly Dictionary StIndOpcodes = new()
{
@@ -48,18 +52,52 @@ internal static unsafe class InjectorHelpers
[typeof(double)] = OpCodes.Stind_R8
};
- private static void CreateInjectedAssembly()
+ private static void CreateDefaultInjectedAssembly()
+ {
+ DefaultInjectedImage ??= CreateInjectedImage(InjectedMonoTypesAssemblyName);
+ }
+
+ 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();
+
+ var nameNoExt = name;
+ if (nameNoExt.EndsWith(".dll"))
+ nameNoExt = name.Replace(".dll", "");
- InjectedAssembly.Name.Name = Marshal.StringToHGlobalAnsi("InjectedMonoTypes");
+ assembly.Name.Name = Marshal.StringToHGlobalAnsi(nameNoExt);
- InjectedImage.Assembly = InjectedAssembly.AssemblyPointer;
- InjectedImage.Dynamic = 1;
- InjectedImage.Name = InjectedAssembly.Name.Name;
- if (InjectedImage.HasNameNoExt)
- InjectedImage.NameNoExt = InjectedAssembly.Name.Name;
+ image.Assembly = assembly.AssemblyPointer;
+ image.Dynamic = 1;
+ image.Name = Marshal.StringToHGlobalAnsi(name);
+ if (image.HasNameNoExt)
+ image.NameNoExt = assembly.Name.Name;
+
+ assembly.Image = image.ImagePointer;
+ InjectedImages.Add(name, image.Pointer);
+ return image;
+ }
+
+ internal static bool TryGetInjectedImage(string name, out IntPtr ptr)
+ {
+ if (!name.EndsWith(".dll"))
+ name += ".dll";
+ return InjectedImages.TryGetValue(name, out ptr);
+ }
+
+ internal static bool TryGetInjectedImageForAssembly(Assembly assembly, out IntPtr 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();
@@ -69,9 +107,15 @@ 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 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 (InjectedAssembly == null) CreateInjectedAssembly();
+ CreateDefaultInjectedAssembly();
GenericMethodGetMethodHook.ApplyHook();
GetTypeInfoFromTypeDefinitionIndexHook.ApplyHook();
GetFieldDefaultValueHook.ApplyHook();
@@ -79,6 +123,131 @@ internal static void Setup()
FromIl2CppTypeHook.ApplyHook();
FromNameHook.ApplyHook();
RunFinalizerPatch.ApplyHook();
+
+ //AssemblyGetLoadedAssemblyHook.ApplyHook();
+ //AppDomainGetAssembliesHook.ApplyHook();
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct WIN32_FILE_ATTRIBUTE_DATA
+ {
+ 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);
+ filePath = filePath.Replace(@"\\?\", "");
+
+ if (AssemblyListFile.IsTargetFile(filePath))
+ {
+ AssemblyListFile.Setup(filePath);
+ foreach (var assemblyName in InjectedImages.Keys)
+ {
+ AssemblyListFile.AddAssembly(assemblyName);
+ }
+
+ 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);
+ 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 (AssemblyListFile.IsTargetFile(filePath))
+ {
+ IntPtr newHandle = CreateFile(AssemblyListFile.GetOrCreateNewFile(), FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
+ return ReadFile(newHandle, bytes, numBytesToRead, numBytesRead, overlapped);
+ }
+ }
+
+ return ReadFile(handle, bytes, numBytesToRead, numBytesRead, overlapped);
+ }
+
+ 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
+ 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);
+ }
+ }
+
+ AssemblyListFile = UnityVersionHandler.GetAssemblyListFile();
+
+ 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();
}
internal static long CreateClassToken(IntPtr classPointer)
@@ -89,12 +258,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(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())
{
@@ -137,28 +310,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()
@@ -170,11 +336,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");
@@ -182,8 +350,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);
@@ -198,6 +368,7 @@ static nint GetClassInitSubstitute()
return Marshal.GetDelegateForFunctionPointer(pClassInit);
}
+
#endregion
}
}
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()
{
diff --git a/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs b/Il2CppInterop.Runtime/Startup/Il2CppInteropRuntime.cs
index 3147986b..e600b8ff 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;
@@ -37,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();
}