// See: IL2CPP/Il2CppBinary.cs // See: IL2CPP/Il2CppBinaryClasses.cs public void PostProcessBinary(Il2CppBinary binary, PluginPostProcessBinaryEventInfo data) { // This is called once per IL2CPP binary after it has been fully loaded // This is a direct parsing of the structs in the binary file // with some items re-arranged into eg. Dictionary collections for simplified access // CodeRegistration, MetadataRegistration, CodeGenModulePointers, // FieldOffsetPointers, MethodSpecs, GenericInstances, TypeReferences etc. etc. // See Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs for a complete list // Note that some items are only valid for certain IL2CPP versions // Use the Image.Version property to check the IL2CPP version // Example: list all CodeGenModules /* Module mscorlib.dll has 11863 methods * Module Mono.Security.dll has 421 methods * Module System.Xml.dll has 1 methods * Module System.dll has 3623 methods ... */ if (binary.Image.Version >= 24.2) { foreach (var module in binary.Modules) { Console.WriteLine($"Module {module.Key} has {module.Value.methodPointerCount} methods"); } } // Set data.IsDataModified if you modify the Il2CppBinary // Set data.IsStreamModified if you modify the stream contents }
// See: IL2CPP/Il2CppBinary.cs public void PreProcessBinary(Il2CppBinary binary, PluginPreProcessBinaryEventInfo data) { // This is called once per found IL2CPP binary // after Il2CppCodeRegistration and Il2CppMetadataRegistration have been located and read // into binary.CodeRegistration and binary.MetadataRegistration, // but they have not been validated and no other loading or analysis has been performed // Example: check that the found structs make sense if (binary.CodeRegistration.interopDataCount > 0x1000) { // This is very unlikely } // You can acquire the underlying IFileFormatStream (BinaryObjectStream) with binary.Image var underlyingStream = binary.Image; // Set data.IsDataModified if you modify the Il2CppBinary // Set data.IsStreamModified if you modify the stream contents }
// Handle ROT name encryption found in some binaries public void PreProcessBinary(Il2CppBinary binary, PluginPreProcessBinaryEventInfo data) { // Get all exports var exports = binary.Image.GetExports()?.ToList(); if (exports == null) { return; } // Try every ROT possibility (except 0 - these will already be added to APIExports) var exportRgx = new Regex(@"^_+"); for (var rotKey = 1; rotKey <= 25; rotKey++) { var possibleExports = exports.Select(e => new { Name = string.Join("", e.Name.Select(x => (char)(x >= 'a' && x <= 'z'? (x - 'a' + rotKey) % 26 + 'a' : x))), VirtualAddress = e.VirtualAddress }).ToList(); var foundExports = possibleExports .Where(e => (e.Name.StartsWith("il2cpp_") || e.Name.StartsWith("_il2cpp_") || e.Name.StartsWith("__il2cpp_")) && !e.Name.Contains("il2cpp_z_")) .Select(e => e); if (foundExports.Any() && !data.IsDataModified) { PluginServices.For(this).StatusUpdate("Decrypting API export names"); data.IsDataModified = true; } foreach (var export in foundExports) { if (binary.Image.TryMapVATR(export.VirtualAddress, out _)) { binary.APIExports.Add(exportRgx.Replace(export.Name, ""), export.VirtualAddress); } } } }
private void LoadIlAppModel() { // Lazily create an Il2CppInspector model. Copying the ELF data array is required since the library // seems to mess with the data, which crashes the game. // TODO: Consider caching the result in a file. This is horribly slow. var binaryStream = new MemoryStream(Data.ToArray()); var metadataStream = new MemoryStream(Il2CppMetadata); var oldOut = Console.Out; // Disable Console.WriteLine() since Il2CppInspector logs text Console.SetOut(TextWriter.Null); try { // The internal class Il2CppInspector.ElfReader64 can be used to retrieve the offsets of sections // in the ELF file. These are relevant since Il2CppInspector saves symbol offsets relative to the // code section of the ELF so this offset needs to be considered when editing the ELF. var elfReader = CreateAndInitElfReader(binaryStream); ReadSectionOffsets(elfReader); // Create the inspector manually instead of using Il2CppInspector.LoadFromStream to avoid // reading the ELF a second time. var metadata = new Metadata(metadataStream); var binary = Il2CppBinary.Load(elfReader, metadata); var inspector = new Il2CppInspector.Il2CppInspector(binary, metadata); if (inspector == null) { throw new Exception("Couldn't extract Il2Cpp metadata."); } ilAppModel = new AppModel(new TypeModel(inspector)); } finally { Console.SetOut(oldOut); } }
// Guess which header file(s) correspond to the given metadata+binary. // Note that this may match multiple headers due to structural changes between versions // that are not reflected in the metadata version. public static List <UnityHeaders> GuessHeadersForBinary(Il2CppBinary binary) { List <UnityResource> typeHeaders = new List <UnityResource>(); foreach (var r in GetAllTypeHeaders()) { var metadataVersion = GetMetadataVersionFromFilename(r.Name); if (metadataVersion != binary.Image.Version) { continue; } if (metadataVersion == 21) { /* Special version logic for metadata version 21 based on the Il2CppMetadataRegistration.fieldOffsets field */ var headerFieldOffsetsArePointers = r.VersionRange.Min.CompareTo("5.3.7") >= 0 && r.VersionRange.Min.CompareTo("5.4.0") != 0; var binaryFieldOffsetsArePointers = binary.FieldOffsets == null; if (headerFieldOffsetsArePointers != binaryFieldOffsetsArePointers) { continue; } } typeHeaders.Add(r); } // Get total range of selected headers // Sort is needed because 5.x.x comes before 20xx.x.x in the resource list typeHeaders = typeHeaders.OrderBy(x => x.VersionRange).ToList(); var totalRange = new UnityVersionRange(typeHeaders.First().VersionRange.Min, typeHeaders.Last().VersionRange.Max); // Get all API versions in this range var apis = GetAllAPIHeaders().Where(a => a.VersionRange.Intersect(totalRange) != null).ToList(); // Get the API exports for the binary var exports = binary.GetAPIExports(); // No il2cpp exports? Just return the earliest version from the header range // The API version may be incorrect but should be a subset of the real API and won't cause C++ compile errors if (!exports.Any()) { Console.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project"); return(typeHeaders.Select(t => new UnityHeaders(t, apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList()); } // Go through all of the possible API versions and see how closely they match the binary // Note: if apis.Count == 1, we can't actually narrow down the version range further, // but we still need to check that the APIs actually exist in the binary var apiMatches = new List <UnityResource>(); foreach (var api in apis) { var apiFunctionList = GetFunctionNamesFromAPIHeaderText(api.GetText()); // Every single function in the API list must be an export for a match if (!apiFunctionList.Except(exports.Keys).Any()) { apiMatches.Add(api); } } if (apiMatches.Any()) { // Intersect all API ranges with all header ranges to produce final list of possible ranges Console.WriteLine("IL2CPP API discovery was successful"); return(typeHeaders.SelectMany( t => apiMatches.Where(a => t.VersionRange.Intersect(a.VersionRange) != null) .Select(a => new UnityHeaders(t, a))).ToList()); } // None of the possible API versions match the binary // Select the oldest API version from the group - C++ project compilation will fail Console.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial"); return(typeHeaders.Select(t => new UnityHeaders(t, apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList()); }