From ef1a5d1e83ab075e53f63046507048ab13ec2224 Mon Sep 17 00:00:00 2001 From: LukeFZ <17146677+LukeFZ@users.noreply.github.com> Date: Thu, 30 Nov 2023 13:31:37 +0100 Subject: [PATCH] Add BlobReader, fix Il2CppType for 27.2+ --- .../IL2CPP/Il2CppBinaryClasses.cs | 10 +- .../IL2CPP/Il2CppInspector.cs | 1206 ++++++++--------- .../Reflection/TypeModel.cs | 2 +- Il2CppInspector.Common/Utils/BlobReader.cs | 137 ++ 4 files changed, 723 insertions(+), 632 deletions(-) create mode 100644 Il2CppInspector.Common/Utils/BlobReader.cs diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs b/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs index a1c4ce4..45e81b1 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs @@ -218,10 +218,12 @@ namespace Il2CppInspector public uint attrs => (uint) bits & 0xffff; /* param attributes or field flags */ public Il2CppTypeEnum type => (Il2CppTypeEnum)((bits >> 16) & 0xff); - // TODO: Unity 2021.1 (v27.2): num_mods becomes 1 bit shorter, shifting byref and pinned right 1 bit, valuetype bit added - public uint num_mods => (uint) (bits >> 24) & 0x3f; /* max 64 modifiers follow at the end */ - public bool byref => ((bits >> 30) & 1) == 1; - public bool pinned => (bits >> 31) == 1; /* valid when included in a local var signature */ + // Unity 2021.1 (v27.2): num_mods becomes 1 bit shorter, shifting byref and pinned left 1 bit, valuetype bit added + + public uint NumMods(double version) => (uint) (bits >> 24) & (version >= 27.2 ? 0x1fu : 0x3fu); + public bool ByRef(double version) => (bits >> (version >= 27.2 ? 29 : 30) & 1) == 1; + public bool Pinned(double version) => (bits >> (version >= 27.2 ? 30 : 31) & 1) == 1; + public bool ValueType(double version) => version >= 27.2 && ((bits >> 31) & 1) == 1; // Was only added in 27.2 /*public class Union { diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs index bb4a2c1..8bc0cc3 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs @@ -1,627 +1,579 @@ -/* - Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using Il2CppInspector.Utils; -using NoisyCowStudios.Bin2Object; -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; - -namespace Il2CppInspector -{ - // Il2CppInspector ties together the binary and metadata files into a congruent API surface - public class Il2CppInspector - { - public Il2CppBinary Binary { get; } - public Metadata Metadata { get; } - - // All function pointers including attribute initialization functions etc. (start => end) - public Dictionary FunctionAddresses { get; } - - // Attribute indexes (>=24.1) arranged by customAttributeStart and token - public Dictionary> AttributeIndicesByToken { get; } - - // Merged list of all metadata usage references - public List MetadataUsages { get; } - - // Shortcuts - public double Version => Math.Max(Metadata.Version, Binary.Image.Version); - - public Dictionary Strings => Metadata.Strings; - public string[] StringLiterals => Metadata.StringLiterals; - public Il2CppTypeDefinition[] TypeDefinitions => Metadata.Types; - public Il2CppAssemblyDefinition[] Assemblies => Metadata.Assemblies; - public Il2CppImageDefinition[] Images => Metadata.Images; - public Il2CppMethodDefinition[] Methods => Metadata.Methods; - public Il2CppParameterDefinition[] Params => Metadata.Params; - public Il2CppFieldDefinition[] Fields => Metadata.Fields; - public Il2CppPropertyDefinition[] Properties => Metadata.Properties; - public Il2CppEventDefinition[] Events => Metadata.Events; - public Il2CppGenericContainer[] GenericContainers => Metadata.GenericContainers; - public Il2CppGenericParameter[] GenericParameters => Metadata.GenericParameters; - public int[] GenericConstraintIndices => Metadata.GenericConstraintIndices; - public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges => Metadata.AttributeTypeRanges; - public Il2CppInterfaceOffsetPair[] InterfaceOffsets => Metadata.InterfaceOffsets; - public int[] InterfaceUsageIndices => Metadata.InterfaceUsageIndices; - public int[] NestedTypeIndices => Metadata.NestedTypeIndices; - public int[] AttributeTypeIndices => Metadata.AttributeTypeIndices; - public uint[] VTableMethodIndices => Metadata.VTableMethodIndices; - public Il2CppFieldRef[] FieldRefs => Metadata.FieldRefs; - public Dictionary FieldDefaultValue { get; } = new Dictionary(); - public Dictionary ParameterDefaultValue { get; } = new Dictionary(); - public List FieldOffsets { get; } - public List TypeReferences => Binary.TypeReferences; - public Dictionary TypeReferenceIndicesByAddress => Binary.TypeReferenceIndicesByAddress; - public List GenericInstances => Binary.GenericInstances; - public Dictionary Modules => Binary.Modules; - public ulong[] CustomAttributeGenerators { get; } - public ulong[] MethodInvokePointers { get; } - public Il2CppMethodSpec[] MethodSpecs => Binary.MethodSpecs; - public Dictionary GenericMethodPointers { get; } - public Dictionary GenericMethodInvokerIndices => Binary.GenericMethodInvokerIndices; - - // TODO: Finish all file access in the constructor and eliminate the need for this - public IFileFormatStream BinaryImage => Binary.Image; - - private (ulong MetadataAddress, object Value)? getDefaultValue(int typeIndex, int dataIndex) { - // No default - if (dataIndex == -1) - return (0ul, null); - - // Get pointer in binary to default value - var pValue = Metadata.Header.fieldAndParameterDefaultValueDataOffset + dataIndex; - var typeRef = TypeReferences[typeIndex]; - - // Default value is null - if (pValue == 0) - return (0ul, null); - - object value = null; - Metadata.Position = pValue; - switch (typeRef.type) { - case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: - value = Metadata.ReadBoolean(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U1: - case Il2CppTypeEnum.IL2CPP_TYPE_I1: - value = Metadata.ReadByte(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: - // UTF-8 character assumed - value = BitConverter.ToChar(Metadata.ReadBytes(2), 0); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U2: - value = Metadata.ReadUInt16(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_I2: - value = Metadata.ReadInt16(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U4: - value = Metadata.Version >= 29 - ? Metadata.ReadCompressedUInt32() - : Metadata.ReadUInt32(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_I4: - value = Metadata.Version >= 29 - ? Metadata.ReadCompressedInt32() - : Metadata.ReadInt32(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_U8: - value = Metadata.ReadUInt64(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_I8: - value = Metadata.ReadInt64(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_R4: - value = Metadata.ReadSingle(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_R8: - value = Metadata.ReadDouble(); - break; - case Il2CppTypeEnum.IL2CPP_TYPE_STRING: - var uiLen = Metadata.Version >= 29 - ? Metadata.ReadCompressedInt32() - : Metadata.ReadInt32(); - - value = Encoding.UTF8.GetString(Metadata.ReadBytes(uiLen)); - break; - } - return ((ulong) pValue, value); - } - - private List buildMetadataUsages() - { - // No metadata usages for versions < 19 - if (Version < 19) - return null; - - // Metadata usages are lazily initialized during runtime for versions >= 27 - if (Version >= 27) - return buildLateBindingMetadataUsages(); - - // Version >= 19 && < 27 - var usages = new Dictionary(); - foreach (var metadataUsageList in Metadata.MetadataUsageLists) - { - for (var i = 0; i < metadataUsageList.count; i++) - { - var metadataUsagePair = Metadata.MetadataUsagePairs[metadataUsageList.start + i]; - usages.TryAdd(metadataUsagePair.destinationindex, MetadataUsage.FromEncodedIndex(this, metadataUsagePair.encodedSourceIndex)); - } - } - - // Metadata usages (addresses) - // Unfortunately the value supplied in MetadataRegistration.matadataUsagesCount seems to be incorrect, - // so we have to calculate the correct number of usages above before reading the usage address list from the binary - var count = usages.Keys.Max() + 1; - var addresses = Binary.Image.ReadMappedArray(Binary.MetadataRegistration.metadataUsages, (int) count); - foreach (var usage in usages) - usage.Value.SetAddress(addresses[usage.Key]); - - return usages.Values.ToList(); - } - - private List buildLateBindingMetadataUsages() - { - // plagiarism. noun - https://www.lexico.com/en/definition/plagiarism - // the practice of taking someone else's work or ideas and passing them off as one's own. - // Synonyms: copying, piracy, theft, strealing, infringement of copyright - - BinaryImage.Position = 0; - var words = BinaryImage.ReadArray(0, (int)BinaryImage.Length / (BinaryImage.Bits / 8)); - var usages = new List(); - - for (uint i = 0; i < words.Length; i++) - { - var metadataValue = words[i]; - if (metadataValue < uint.MaxValue) - { - var encodedToken = (uint)metadataValue; - var usage = MetadataUsage.FromEncodedIndex(this, encodedToken); - - if (CheckMetadataUsageSanity(usage) - && BinaryImage.TryMapFileOffsetToVA(i * ((uint)BinaryImage.Bits / 8), out var va)) - { - usage.SetAddress(va); - usages.Add(usage); - } - } - } - - return usages; - - bool CheckMetadataUsageSanity(MetadataUsage usage) - { - return usage.Type switch - { - MetadataUsageType.TypeInfo or MetadataUsageType.Type => TypeReferences.Count > usage.SourceIndex, - MetadataUsageType.MethodDef => Methods.Length > usage.SourceIndex, - MetadataUsageType.FieldInfo or MetadataUsageType.FieldRva => FieldRefs.Length > usage.SourceIndex, - MetadataUsageType.StringLiteral => StringLiterals.Length > usage.SourceIndex, - MetadataUsageType.MethodRef => MethodSpecs.Length > usage.SourceIndex, - _ => false, - }; - } - } - - // Thumb instruction pointers have the bottom bit set to signify a switch from ARM to Thumb when jumping - private ulong getDecodedAddress(ulong addr) { - if (BinaryImage.Arch != "ARM" && BinaryImage.Arch != "ARM64") - return addr; - - return addr & 0xffff_ffff_ffff_fffe; - } - - public Il2CppInspector(Il2CppBinary binary, Metadata metadata) { - // Store stream representations - Binary = binary; - Metadata = metadata; - - // Get all field default values - foreach (var fdv in Metadata.FieldDefaultValues) - FieldDefaultValue.Add(fdv.fieldIndex, ((ulong,object)) getDefaultValue(fdv.typeIndex, fdv.dataIndex)); - - // Get all parameter default values - foreach (var pdv in Metadata.ParameterDefaultValues) - ParameterDefaultValue.Add(pdv.parameterIndex, ((ulong,object)) getDefaultValue(pdv.typeIndex, pdv.dataIndex)); - - // Get all field offsets - if (Binary.FieldOffsets != null) { - FieldOffsets = Binary.FieldOffsets.Select(x => (long) x).ToList(); - } - - // Convert pointer list into fields - else { - var offsets = new Dictionary(); - for (var i = 0; i < TypeDefinitions.Length; i++) { - var def = TypeDefinitions[i]; - var pFieldOffsets = Binary.FieldOffsetPointers[i]; - if (pFieldOffsets != 0) { - bool available = true; - - // If the target address range is not mapped in the file, assume zeroes - try { - BinaryImage.Position = BinaryImage.MapVATR((ulong) pFieldOffsets); - } - catch (InvalidOperationException) { - available = false; - } - - for (var f = 0; f < def.field_count; f++) - offsets.Add(def.fieldStart + f, available? BinaryImage.ReadUInt32() : 0); - } - } - - FieldOffsets = offsets.OrderBy(x => x.Key).Select(x => x.Value).ToList(); - } - - // Build list of custom attribute generators - if (Version < 27) - CustomAttributeGenerators = Binary.CustomAttributeGenerators; - - else { - var cagCount = Images.Sum(i => i.customAttributeCount); - CustomAttributeGenerators = new ulong[cagCount]; - - foreach (var image in Images) { - // Get CodeGenModule for this image - var codeGenModule = Binary.Modules[Strings[image.nameIndex]]; - var cags = BinaryImage.ReadMappedWordArray(codeGenModule.customAttributeCacheGenerator, (int) image.customAttributeCount); - cags.CopyTo(CustomAttributeGenerators, image.customAttributeStart); - } - } - - // Decode addresses for Thumb etc. without altering the Il2CppBinary structure data - CustomAttributeGenerators = CustomAttributeGenerators.Select(a => getDecodedAddress(a)).ToArray(); - MethodInvokePointers = Binary.MethodInvokePointers.Select(a => getDecodedAddress(a)).ToArray(); - GenericMethodPointers = Binary.GenericMethodPointers.ToDictionary(a => a.Key, a => getDecodedAddress(a.Value)); - - // Get sorted list of function pointers from all sources - // TODO: This does not include IL2CPP API functions - var sortedFunctionPointers = (Version <= 24.1)? - Binary.GlobalMethodPointers.Select(a => getDecodedAddress(a)).ToList() : - Binary.ModuleMethodPointers.SelectMany(module => module.Value).Select(a => getDecodedAddress(a)).ToList(); - - sortedFunctionPointers.AddRange(CustomAttributeGenerators); - sortedFunctionPointers.AddRange(MethodInvokePointers); - sortedFunctionPointers.AddRange(GenericMethodPointers.Values); - sortedFunctionPointers.Sort(); - sortedFunctionPointers = sortedFunctionPointers.Distinct().ToList(); - - // Guestimate function end addresses - FunctionAddresses = new Dictionary(sortedFunctionPointers.Count); - for (var i = 0; i < sortedFunctionPointers.Count - 1; i++) - FunctionAddresses.Add(sortedFunctionPointers[i], sortedFunctionPointers[i + 1]); - // The last method end pointer will be incorrect but there is no way of calculating it - FunctionAddresses.Add(sortedFunctionPointers[^1], sortedFunctionPointers[^1]); - - // Organize custom attribute indices - if (Version >= 24.1 && Version < 29) { - AttributeIndicesByToken = new Dictionary>(); - foreach (var image in Images) { - var attsByToken = new Dictionary(); - for (int i = 0; i < image.customAttributeCount; i++) { - var index = image.customAttributeStart + i; - var token = AttributeTypeRanges[index].token; - attsByToken.Add(token, index); - } - if (image.customAttributeCount > 0) - AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken); - } - } - - // Merge all metadata usage references into a single distinct list - MetadataUsages = buildMetadataUsages(); - - // Plugin hook PostProcessPackage - PluginHooks.PostProcessPackage(this); - } - - // Get a method pointer if available - public (ulong Start, ulong End)? GetMethodPointer(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) { - // Find method pointer - if (methodDef.methodIndex < 0) - return null; - - ulong start = 0; - - // Global method pointer array - if (Version <= 24.1) { - start = Binary.GlobalMethodPointers[methodDef.methodIndex]; - } - - // Per-module method pointer array uses the bottom 24 bits of the method's metadata token - // Derived from il2cpp::vm::MetadataCache::GetMethodPointer - if (Version >= 24.2) { - var method = (methodDef.token & 0xffffff); - if (method == 0) - return null; - - // In the event of an exception, the method pointer is not set in the file - // This probably means it has been optimized away by the compiler, or is an unused generic method - try { - // Remove ARM Thumb marker LSB if necessary - start = Binary.ModuleMethodPointers[module][method - 1]; - } - catch (IndexOutOfRangeException) { - return null; - } - } - - if (start == 0) - return null; - - // Consider the end of the method to be the start of the next method (or zero) - // The last method end will be wrong but there is no way to calculate it - start = getDecodedAddress(start); - return (start, FunctionAddresses[start]); - } - - // Get a concrete generic method pointer if available - public (ulong Start, ulong End)? GetGenericMethodPointer(Il2CppMethodSpec spec) { - if (GenericMethodPointers.TryGetValue(spec, out var start)) { - return (start, FunctionAddresses[start]); - } - return null; - } - - // Get a method invoker index from a method definition - public int GetInvokerIndex(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) { - if (Version <= 24.1) { - return methodDef.invokerIndex; - } - - // Version >= 24.2 - var methodInModule = (methodDef.token & 0xffffff); - return Binary.MethodInvokerIndices[module][methodInModule - 1]; - } - - public MetadataUsage[] GetVTable(Il2CppTypeDefinition definition) { - MetadataUsage[] res = new MetadataUsage[definition.vtable_count]; - for (int i = 0; i < definition.vtable_count; i++) { - var encodedIndex = VTableMethodIndices[definition.vtableStart + i]; - MetadataUsage usage = MetadataUsage.FromEncodedIndex(this, encodedIndex); - if (usage.SourceIndex != 0) - res[i] = usage; - } - return res; - } - - #region Loaders - // Finds and extracts the metadata and IL2CPP binary from one or more APK files, or one AAB or IPA file into MemoryStreams - // Returns null if package not recognized or does not contain an IL2CPP application - public static (MemoryStream Metadata, MemoryStream Binary)? GetStreamsFromPackage(IEnumerable zipStreams, bool silent = false) { - try { - MemoryStream metadataMemoryStream = null, binaryMemoryStream = null; - ZipArchiveEntry androidAAB = null; - ZipArchiveEntry ipaBinaryFolder = null; - var binaryFiles = new List(); - - // Iterate over each archive looking for the wanted files - // There are three possibilities: - // - A single IPA file containing global-metadata.dat and a single binary supporting one or more architectures - // (we return the binary inside the IPA to be loaded by MachOReader for single arch or UBReader for multi arch) - // - A single APK or AAB file containing global-metadata.dat and one or more binaries (one per architecture) - // (we return the entire APK or AAB to be loaded by APKReader or AABReader) - // - Multiple APK files, one of which contains global-metadadata.dat and the others contain one binary each - // (we return all of the binaries re-packed in memory to a new Zip file, to be loaded by APKReader) - - // We can't close the files because we might have to read from them after the foreach - foreach (var zip in zipStreams) { - - // Check for Android APK (split APKs will only fill one of these two variables) - var metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == "assets/bin/Data/Managed/Metadata/global-metadata.dat"); - binaryFiles.AddRange(zip.Entries.Where(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so")); - - // Check for Android AAB - androidAAB = zip.Entries.FirstOrDefault(f => f.FullName == "base/resources.pb"); - - if (androidAAB != null) { - metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == "base/assets/bin/Data/Managed/Metadata/global-metadata.dat"); - binaryFiles.AddRange(zip.Entries.Where(f => f.FullName.StartsWith("base/lib/") && f.Name == "libil2cpp.so")); - } - - // Check for iOS IPA - ipaBinaryFolder = zip.Entries.FirstOrDefault(f => f.FullName.StartsWith("Payload/") && f.FullName.EndsWith(".app/") && f.FullName.Count(x => x == '/') == 2); - - if (ipaBinaryFolder != null) { - var ipaBinaryName = ipaBinaryFolder.FullName[8..^5]; - metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == $"Payload/{ipaBinaryName}.app/Data/Managed/Metadata/global-metadata.dat"); - binaryFiles.AddRange(zip.Entries.Where(f => f.FullName == $"Payload/{ipaBinaryName}.app/{ipaBinaryName}")); - } - - // Found metadata? - if (metadataFile != null) { - // Extract the metadata file to memory - if (!silent) - Console.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}"); - - metadataMemoryStream = new MemoryStream(); - using var metadataStream = metadataFile.Open(); - metadataStream.CopyTo(metadataMemoryStream); - metadataMemoryStream.Position = 0; - } - } - - // This package doesn't contain an IL2CPP application - if (metadataMemoryStream == null || !binaryFiles.Any()) { - Console.Error.WriteLine($"Package does not contain a complete IL2CPP application"); - return null; - } - - // IPAs will only have one binary (which may or may not be a UB covering multiple architectures) - if (ipaBinaryFolder != null) { - if (!silent) - Console.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}"); - - // Extract the binary file or package to memory - binaryMemoryStream = new MemoryStream(); - using var binaryStream = binaryFiles.First().Open(); - binaryStream.CopyTo(binaryMemoryStream); - binaryMemoryStream.Position = 0; - } - - // AABs or single APKs may have one or more binaries, one per architecture - // Split APKs will have one binary per APK - // Roll them up into a new in-memory zip file and load it via AABReader/APKReader - else { - binaryMemoryStream = new MemoryStream(); - using (var apkArchive = new ZipArchive(binaryMemoryStream, ZipArchiveMode.Create, true)) { - foreach (var binary in binaryFiles) { - // Don't waste time re-compressing data we just uncompressed - var archiveFile = apkArchive.CreateEntry(binary.FullName, CompressionLevel.NoCompression); - using var archiveFileStream = archiveFile.Open(); - using var binarySourceStream = binary.Open(); - binarySourceStream.CopyTo(archiveFileStream); - } - } - binaryMemoryStream.Position = 0; - } - - return (metadataMemoryStream, binaryMemoryStream); - } - // Not an archive - catch (InvalidDataException) { - return null; - } - } - - public static (MemoryStream Metadata, MemoryStream Binary)? GetStreamsFromPackage(IEnumerable packageFiles, bool silent = false) { - // Check every item is a zip file first because ZipFile.OpenRead is extremely slow if it isn't - foreach (var file in packageFiles) - using (BinaryReader zipTest = new BinaryReader(File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))) { - if (zipTest.ReadUInt32() != 0x04034B50) - return null; - } - - // Check for an XAPK/Zip-style file - if (packageFiles.Count() == 1) { - try { - var xapk = ZipFile.OpenRead(packageFiles.First()); - var apks = xapk.Entries.Where(f => f.FullName.EndsWith(".apk")); - - // An XAPK/Zip file containing one or more APKs. Extract them - if (apks.Any()) { - var apkFiles = new List(); - foreach (var apk in apks) { - var bytes = new MemoryStream(); - using var apkStream = apk.Open(); - apkStream.CopyTo(bytes); - apkFiles.Add(bytes); - } - return GetStreamsFromPackage(apkFiles.Select(f => new ZipArchive(f, ZipArchiveMode.Read))); - } - } - // Not an archive - catch (InvalidDataException) { - return null; - } - } - - return GetStreamsFromPackage(packageFiles.Select(f => ZipFile.OpenRead(f)), silent); - } - - // Load from an AAB, IPA or one or more APK files - public static List LoadFromPackage(IEnumerable packageFiles, LoadOptions loadOptions = null, EventHandler statusCallback = null, bool silent = false) { - var streams = GetStreamsFromPackage(packageFiles, silent); - if (!streams.HasValue) - return null; - return LoadFromStream(streams.Value.Binary, streams.Value.Metadata, loadOptions, statusCallback, silent); - } - - // Load from a binary file and metadata file - public static List LoadFromFile(string binaryFile, string metadataFile, LoadOptions loadOptions = null, EventHandler statusCallback = null, bool silent = false) - => LoadFromStream(new FileStream(binaryFile, FileMode.Open, FileAccess.Read, FileShare.Read), - new MemoryStream(File.ReadAllBytes(metadataFile)), - loadOptions, statusCallback, silent); - - // Load from a binary stream and metadata stream - // Must be a seekable stream otherwise we catch a System.IO.NotSupportedException - public static List LoadFromStream(Stream binaryStream, MemoryStream metadataStream, LoadOptions loadOptions = null, EventHandler statusCallback = null, bool silent = false) { - - // Silent operation if requested - var stdout = Console.Out; - if (silent) - Console.SetOut(new StreamWriter(Stream.Null)); - - // Load the metadata file - Metadata metadata; - try { - metadata = Metadata.FromStream(metadataStream, statusCallback); - } - catch (Exception ex) { - Console.Error.WriteLine(ex.Message); - Console.SetOut(stdout); - return null; - } - - Console.WriteLine("Detected metadata version " + metadata.Version); - - // Load the il2cpp code file (try all available file formats) - IFileFormatStream stream; - try { - stream = FileFormatStream.Load(binaryStream, loadOptions, statusCallback); - - if (stream == null) - throw new InvalidOperationException("Unsupported executable file format"); - } - catch (Exception ex) { - Console.Error.WriteLine(ex.Message); - Console.SetOut(stdout); - return null; - } - - // Multi-image binaries may contain more than one Il2Cpp image - var inspectors = LoadFromStream(stream, metadata, statusCallback); - - Console.SetOut(stdout); - - return inspectors; - } - - public static List LoadFromStream(IFileFormatStream stream, Metadata metadata, EventHandler statusCallback = null) { - - var processors = new List(); - foreach (var image in stream.Images) { - Console.WriteLine("Container format: " + image.Format); - Console.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness); - Console.WriteLine("Architecture word size: {0}-bit", image.Bits); - Console.WriteLine("Instruction set: " + image.Arch); - Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset); - - // Architecture-agnostic load attempt - try { - if (Il2CppBinary.Load(image, metadata, statusCallback) is Il2CppBinary binary) { - Console.WriteLine("IL2CPP binary version " + image.Version); - - processors.Add(new Il2CppInspector(binary, metadata)); - } - else { - Console.Error.WriteLine("Could not process IL2CPP image. This may mean the binary file is packed, encrypted or obfuscated in a way Il2CppInspector cannot process, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data."); - Console.Error.WriteLine("Please ensure you have downloaded and installed the latest set of core plugins and try again. Check the binary file in a disassembler to ensure that it is a valid IL2CPP binary before submitting a bug report!"); - } - } - // Unknown architecture - catch (NotImplementedException ex) { - Console.Error.WriteLine(ex.Message); - } - } - - // Plugin hook LoadPipelineEnding - PluginHooks.LoadPipelineEnding(processors); - - return processors; - } - - // Savers - public void SaveMetadataToFile(string pathname) => Metadata.SaveToFile(pathname); - public void SaveBinaryToFile(string pathname) => Binary.SaveToFile(pathname); - #endregion - } -} +/* + Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using Il2CppInspector.Utils; +using NoisyCowStudios.Bin2Object; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; + +namespace Il2CppInspector +{ + // Il2CppInspector ties together the binary and metadata files into a congruent API surface + public class Il2CppInspector + { + public Il2CppBinary Binary { get; } + public Metadata Metadata { get; } + + // All function pointers including attribute initialization functions etc. (start => end) + public Dictionary FunctionAddresses { get; } + + // Attribute indexes (>=24.1) arranged by customAttributeStart and token + public Dictionary> AttributeIndicesByToken { get; } + + // Merged list of all metadata usage references + public List MetadataUsages { get; } + + // Shortcuts + public double Version => Math.Max(Metadata.Version, Binary.Image.Version); + + public Dictionary Strings => Metadata.Strings; + public string[] StringLiterals => Metadata.StringLiterals; + public Il2CppTypeDefinition[] TypeDefinitions => Metadata.Types; + public Il2CppAssemblyDefinition[] Assemblies => Metadata.Assemblies; + public Il2CppImageDefinition[] Images => Metadata.Images; + public Il2CppMethodDefinition[] Methods => Metadata.Methods; + public Il2CppParameterDefinition[] Params => Metadata.Params; + public Il2CppFieldDefinition[] Fields => Metadata.Fields; + public Il2CppPropertyDefinition[] Properties => Metadata.Properties; + public Il2CppEventDefinition[] Events => Metadata.Events; + public Il2CppGenericContainer[] GenericContainers => Metadata.GenericContainers; + public Il2CppGenericParameter[] GenericParameters => Metadata.GenericParameters; + public int[] GenericConstraintIndices => Metadata.GenericConstraintIndices; + public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges => Metadata.AttributeTypeRanges; + public Il2CppInterfaceOffsetPair[] InterfaceOffsets => Metadata.InterfaceOffsets; + public int[] InterfaceUsageIndices => Metadata.InterfaceUsageIndices; + public int[] NestedTypeIndices => Metadata.NestedTypeIndices; + public int[] AttributeTypeIndices => Metadata.AttributeTypeIndices; + public uint[] VTableMethodIndices => Metadata.VTableMethodIndices; + public Il2CppFieldRef[] FieldRefs => Metadata.FieldRefs; + public Dictionary FieldDefaultValue { get; } = new Dictionary(); + public Dictionary ParameterDefaultValue { get; } = new Dictionary(); + public List FieldOffsets { get; } + public List TypeReferences => Binary.TypeReferences; + public Dictionary TypeReferenceIndicesByAddress => Binary.TypeReferenceIndicesByAddress; + public List GenericInstances => Binary.GenericInstances; + public Dictionary Modules => Binary.Modules; + public ulong[] CustomAttributeGenerators { get; } + public ulong[] MethodInvokePointers { get; } + public Il2CppMethodSpec[] MethodSpecs => Binary.MethodSpecs; + public Dictionary GenericMethodPointers { get; } + public Dictionary GenericMethodInvokerIndices => Binary.GenericMethodInvokerIndices; + + // TODO: Finish all file access in the constructor and eliminate the need for this + public IFileFormatStream BinaryImage => Binary.Image; + + private (ulong MetadataAddress, object Value)? getDefaultValue(int typeIndex, int dataIndex) { + // No default + if (dataIndex == -1) + return (0ul, null); + + // Get pointer in binary to default value + var pValue = Metadata.Header.fieldAndParameterDefaultValueDataOffset + dataIndex; + var typeRef = TypeReferences[typeIndex]; + + // Default value is null + if (pValue == 0) + return (0ul, null); + + Metadata.Position = pValue; + var value = BlobReader.GetConstantValueFromBlob(this, typeRef.type, Metadata); + + return ((ulong) pValue, value); + } + + private List buildMetadataUsages() + { + // No metadata usages for versions < 19 + if (Version < 19) + return null; + + // Metadata usages are lazily initialized during runtime for versions >= 27 + if (Version >= 27) + return buildLateBindingMetadataUsages(); + + // Version >= 19 && < 27 + var usages = new Dictionary(); + foreach (var metadataUsageList in Metadata.MetadataUsageLists) + { + for (var i = 0; i < metadataUsageList.count; i++) + { + var metadataUsagePair = Metadata.MetadataUsagePairs[metadataUsageList.start + i]; + usages.TryAdd(metadataUsagePair.destinationindex, MetadataUsage.FromEncodedIndex(this, metadataUsagePair.encodedSourceIndex)); + } + } + + // Metadata usages (addresses) + // Unfortunately the value supplied in MetadataRegistration.matadataUsagesCount seems to be incorrect, + // so we have to calculate the correct number of usages above before reading the usage address list from the binary + var count = usages.Keys.Max() + 1; + var addresses = Binary.Image.ReadMappedArray(Binary.MetadataRegistration.metadataUsages, (int) count); + foreach (var usage in usages) + usage.Value.SetAddress(addresses[usage.Key]); + + return usages.Values.ToList(); + } + + private List buildLateBindingMetadataUsages() + { + // plagiarism. noun - https://www.lexico.com/en/definition/plagiarism + // the practice of taking someone else's work or ideas and passing them off as one's own. + // Synonyms: copying, piracy, theft, strealing, infringement of copyright + + BinaryImage.Position = 0; + var words = BinaryImage.ReadArray(0, (int)BinaryImage.Length / (BinaryImage.Bits / 8)); + var usages = new List(); + + for (uint i = 0; i < words.Length; i++) + { + var metadataValue = words[i]; + if (metadataValue < uint.MaxValue) + { + var encodedToken = (uint)metadataValue; + var usage = MetadataUsage.FromEncodedIndex(this, encodedToken); + + if (CheckMetadataUsageSanity(usage) + && BinaryImage.TryMapFileOffsetToVA(i * ((uint)BinaryImage.Bits / 8), out var va)) + { + usage.SetAddress(va); + usages.Add(usage); + } + } + } + + return usages; + + bool CheckMetadataUsageSanity(MetadataUsage usage) + { + return usage.Type switch + { + MetadataUsageType.TypeInfo or MetadataUsageType.Type => TypeReferences.Count > usage.SourceIndex, + MetadataUsageType.MethodDef => Methods.Length > usage.SourceIndex, + MetadataUsageType.FieldInfo or MetadataUsageType.FieldRva => FieldRefs.Length > usage.SourceIndex, + MetadataUsageType.StringLiteral => StringLiterals.Length > usage.SourceIndex, + MetadataUsageType.MethodRef => MethodSpecs.Length > usage.SourceIndex, + _ => false, + }; + } + } + + // Thumb instruction pointers have the bottom bit set to signify a switch from ARM to Thumb when jumping + private ulong getDecodedAddress(ulong addr) { + if (BinaryImage.Arch != "ARM" && BinaryImage.Arch != "ARM64") + return addr; + + return addr & 0xffff_ffff_ffff_fffe; + } + + public Il2CppInspector(Il2CppBinary binary, Metadata metadata) { + // Store stream representations + Binary = binary; + Metadata = metadata; + + // Get all field default values + foreach (var fdv in Metadata.FieldDefaultValues) + FieldDefaultValue.Add(fdv.fieldIndex, ((ulong,object)) getDefaultValue(fdv.typeIndex, fdv.dataIndex)); + + // Get all parameter default values + foreach (var pdv in Metadata.ParameterDefaultValues) + ParameterDefaultValue.Add(pdv.parameterIndex, ((ulong,object)) getDefaultValue(pdv.typeIndex, pdv.dataIndex)); + + // Get all field offsets + if (Binary.FieldOffsets != null) { + FieldOffsets = Binary.FieldOffsets.Select(x => (long) x).ToList(); + } + + // Convert pointer list into fields + else { + var offsets = new Dictionary(); + for (var i = 0; i < TypeDefinitions.Length; i++) { + var def = TypeDefinitions[i]; + var pFieldOffsets = Binary.FieldOffsetPointers[i]; + if (pFieldOffsets != 0) { + bool available = true; + + // If the target address range is not mapped in the file, assume zeroes + try { + BinaryImage.Position = BinaryImage.MapVATR((ulong) pFieldOffsets); + } + catch (InvalidOperationException) { + available = false; + } + + for (var f = 0; f < def.field_count; f++) + offsets.Add(def.fieldStart + f, available? BinaryImage.ReadUInt32() : 0); + } + } + + FieldOffsets = offsets.OrderBy(x => x.Key).Select(x => x.Value).ToList(); + } + + // Build list of custom attribute generators + if (Version < 27) + CustomAttributeGenerators = Binary.CustomAttributeGenerators; + else if (Version < 29) { + var cagCount = Images.Sum(i => i.customAttributeCount); + CustomAttributeGenerators = new ulong[cagCount]; + + foreach (var image in Images) { + // Get CodeGenModule for this image + var codeGenModule = Binary.Modules[Strings[image.nameIndex]]; + var cags = BinaryImage.ReadMappedWordArray(codeGenModule.customAttributeCacheGenerator, (int) image.customAttributeCount); + cags.CopyTo(CustomAttributeGenerators, image.customAttributeStart); + } + } + + // Decode addresses for Thumb etc. without altering the Il2CppBinary structure data + CustomAttributeGenerators = CustomAttributeGenerators.Select(getDecodedAddress).ToArray(); + MethodInvokePointers = Binary.MethodInvokePointers.Select(getDecodedAddress).ToArray(); + GenericMethodPointers = Binary.GenericMethodPointers.ToDictionary(a => a.Key, a => getDecodedAddress(a.Value)); + + // Get sorted list of function pointers from all sources + // TODO: This does not include IL2CPP API functions + var sortedFunctionPointers = (Version <= 24.1)? + Binary.GlobalMethodPointers.Select(getDecodedAddress).ToList() : + Binary.ModuleMethodPointers.SelectMany(module => module.Value).Select(getDecodedAddress).ToList(); + + sortedFunctionPointers.AddRange(CustomAttributeGenerators); + sortedFunctionPointers.AddRange(MethodInvokePointers); + sortedFunctionPointers.AddRange(GenericMethodPointers.Values); + sortedFunctionPointers.Sort(); + sortedFunctionPointers = sortedFunctionPointers.Distinct().ToList(); + + // Guestimate function end addresses + FunctionAddresses = new Dictionary(sortedFunctionPointers.Count); + for (var i = 0; i < sortedFunctionPointers.Count - 1; i++) + FunctionAddresses.Add(sortedFunctionPointers[i], sortedFunctionPointers[i + 1]); + // The last method end pointer will be incorrect but there is no way of calculating it + FunctionAddresses.Add(sortedFunctionPointers[^1], sortedFunctionPointers[^1]); + + // Organize custom attribute indices + if (Version >= 24.1 && Version < 29) { + AttributeIndicesByToken = new Dictionary>(); + foreach (var image in Images) { + var attsByToken = new Dictionary(); + for (int i = 0; i < image.customAttributeCount; i++) { + var index = image.customAttributeStart + i; + var token = AttributeTypeRanges[index].token; + attsByToken.Add(token, index); + } + if (image.customAttributeCount > 0) + AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken); + } + } + + // Merge all metadata usage references into a single distinct list + MetadataUsages = buildMetadataUsages(); + + // Plugin hook PostProcessPackage + PluginHooks.PostProcessPackage(this); + } + + // Get a method pointer if available + public (ulong Start, ulong End)? GetMethodPointer(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) { + // Find method pointer + if (methodDef.methodIndex < 0) + return null; + + ulong start = 0; + + // Global method pointer array + if (Version <= 24.1) { + start = Binary.GlobalMethodPointers[methodDef.methodIndex]; + } + + // Per-module method pointer array uses the bottom 24 bits of the method's metadata token + // Derived from il2cpp::vm::MetadataCache::GetMethodPointer + if (Version >= 24.2) { + var method = (methodDef.token & 0xffffff); + if (method == 0) + return null; + + // In the event of an exception, the method pointer is not set in the file + // This probably means it has been optimized away by the compiler, or is an unused generic method + try { + // Remove ARM Thumb marker LSB if necessary + start = Binary.ModuleMethodPointers[module][method - 1]; + } + catch (IndexOutOfRangeException) { + return null; + } + } + + if (start == 0) + return null; + + // Consider the end of the method to be the start of the next method (or zero) + // The last method end will be wrong but there is no way to calculate it + start = getDecodedAddress(start); + return (start, FunctionAddresses[start]); + } + + // Get a concrete generic method pointer if available + public (ulong Start, ulong End)? GetGenericMethodPointer(Il2CppMethodSpec spec) { + if (GenericMethodPointers.TryGetValue(spec, out var start)) { + return (start, FunctionAddresses[start]); + } + return null; + } + + // Get a method invoker index from a method definition + public int GetInvokerIndex(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) { + if (Version <= 24.1) { + return methodDef.invokerIndex; + } + + // Version >= 24.2 + var methodInModule = (methodDef.token & 0xffffff); + return Binary.MethodInvokerIndices[module][methodInModule - 1]; + } + + public MetadataUsage[] GetVTable(Il2CppTypeDefinition definition) { + MetadataUsage[] res = new MetadataUsage[definition.vtable_count]; + for (int i = 0; i < definition.vtable_count; i++) { + var encodedIndex = VTableMethodIndices[definition.vtableStart + i]; + MetadataUsage usage = MetadataUsage.FromEncodedIndex(this, encodedIndex); + if (usage.SourceIndex != 0) + res[i] = usage; + } + return res; + } + + #region Loaders + // Finds and extracts the metadata and IL2CPP binary from one or more APK files, or one AAB or IPA file into MemoryStreams + // Returns null if package not recognized or does not contain an IL2CPP application + public static (MemoryStream Metadata, MemoryStream Binary)? GetStreamsFromPackage(IEnumerable zipStreams, bool silent = false) { + try { + MemoryStream metadataMemoryStream = null, binaryMemoryStream = null; + ZipArchiveEntry androidAAB = null; + ZipArchiveEntry ipaBinaryFolder = null; + var binaryFiles = new List(); + + // Iterate over each archive looking for the wanted files + // There are three possibilities: + // - A single IPA file containing global-metadata.dat and a single binary supporting one or more architectures + // (we return the binary inside the IPA to be loaded by MachOReader for single arch or UBReader for multi arch) + // - A single APK or AAB file containing global-metadata.dat and one or more binaries (one per architecture) + // (we return the entire APK or AAB to be loaded by APKReader or AABReader) + // - Multiple APK files, one of which contains global-metadadata.dat and the others contain one binary each + // (we return all of the binaries re-packed in memory to a new Zip file, to be loaded by APKReader) + + // We can't close the files because we might have to read from them after the foreach + foreach (var zip in zipStreams) { + + // Check for Android APK (split APKs will only fill one of these two variables) + var metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == "assets/bin/Data/Managed/Metadata/global-metadata.dat"); + binaryFiles.AddRange(zip.Entries.Where(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so")); + + // Check for Android AAB + androidAAB = zip.Entries.FirstOrDefault(f => f.FullName == "base/resources.pb"); + + if (androidAAB != null) { + metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == "base/assets/bin/Data/Managed/Metadata/global-metadata.dat"); + binaryFiles.AddRange(zip.Entries.Where(f => f.FullName.StartsWith("base/lib/") && f.Name == "libil2cpp.so")); + } + + // Check for iOS IPA + ipaBinaryFolder = zip.Entries.FirstOrDefault(f => f.FullName.StartsWith("Payload/") && f.FullName.EndsWith(".app/") && f.FullName.Count(x => x == '/') == 2); + + if (ipaBinaryFolder != null) { + var ipaBinaryName = ipaBinaryFolder.FullName[8..^5]; + metadataFile = zip.Entries.FirstOrDefault(f => f.FullName == $"Payload/{ipaBinaryName}.app/Data/Managed/Metadata/global-metadata.dat"); + binaryFiles.AddRange(zip.Entries.Where(f => f.FullName == $"Payload/{ipaBinaryName}.app/{ipaBinaryName}")); + } + + // Found metadata? + if (metadataFile != null) { + // Extract the metadata file to memory + if (!silent) + Console.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}"); + + metadataMemoryStream = new MemoryStream(); + using var metadataStream = metadataFile.Open(); + metadataStream.CopyTo(metadataMemoryStream); + metadataMemoryStream.Position = 0; + } + } + + // This package doesn't contain an IL2CPP application + if (metadataMemoryStream == null || !binaryFiles.Any()) { + Console.Error.WriteLine($"Package does not contain a complete IL2CPP application"); + return null; + } + + // IPAs will only have one binary (which may or may not be a UB covering multiple architectures) + if (ipaBinaryFolder != null) { + if (!silent) + Console.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}"); + + // Extract the binary file or package to memory + binaryMemoryStream = new MemoryStream(); + using var binaryStream = binaryFiles.First().Open(); + binaryStream.CopyTo(binaryMemoryStream); + binaryMemoryStream.Position = 0; + } + + // AABs or single APKs may have one or more binaries, one per architecture + // Split APKs will have one binary per APK + // Roll them up into a new in-memory zip file and load it via AABReader/APKReader + else { + binaryMemoryStream = new MemoryStream(); + using (var apkArchive = new ZipArchive(binaryMemoryStream, ZipArchiveMode.Create, true)) { + foreach (var binary in binaryFiles) { + // Don't waste time re-compressing data we just uncompressed + var archiveFile = apkArchive.CreateEntry(binary.FullName, CompressionLevel.NoCompression); + using var archiveFileStream = archiveFile.Open(); + using var binarySourceStream = binary.Open(); + binarySourceStream.CopyTo(archiveFileStream); + } + } + binaryMemoryStream.Position = 0; + } + + return (metadataMemoryStream, binaryMemoryStream); + } + // Not an archive + catch (InvalidDataException) { + return null; + } + } + + public static (MemoryStream Metadata, MemoryStream Binary)? GetStreamsFromPackage(IEnumerable packageFiles, bool silent = false) { + // Check every item is a zip file first because ZipFile.OpenRead is extremely slow if it isn't + foreach (var file in packageFiles) + using (BinaryReader zipTest = new BinaryReader(File.Open(file, FileMode.Open, FileAccess.Read, FileShare.Read))) { + if (zipTest.ReadUInt32() != 0x04034B50) + return null; + } + + // Check for an XAPK/Zip-style file + if (packageFiles.Count() == 1) { + try { + var xapk = ZipFile.OpenRead(packageFiles.First()); + var apks = xapk.Entries.Where(f => f.FullName.EndsWith(".apk")); + + // An XAPK/Zip file containing one or more APKs. Extract them + if (apks.Any()) { + var apkFiles = new List(); + foreach (var apk in apks) { + var bytes = new MemoryStream(); + using var apkStream = apk.Open(); + apkStream.CopyTo(bytes); + apkFiles.Add(bytes); + } + return GetStreamsFromPackage(apkFiles.Select(f => new ZipArchive(f, ZipArchiveMode.Read))); + } + } + // Not an archive + catch (InvalidDataException) { + return null; + } + } + + return GetStreamsFromPackage(packageFiles.Select(f => ZipFile.OpenRead(f)), silent); + } + + // Load from an AAB, IPA or one or more APK files + public static List LoadFromPackage(IEnumerable packageFiles, LoadOptions loadOptions = null, EventHandler statusCallback = null, bool silent = false) { + var streams = GetStreamsFromPackage(packageFiles, silent); + if (!streams.HasValue) + return null; + return LoadFromStream(streams.Value.Binary, streams.Value.Metadata, loadOptions, statusCallback, silent); + } + + // Load from a binary file and metadata file + public static List LoadFromFile(string binaryFile, string metadataFile, LoadOptions loadOptions = null, EventHandler statusCallback = null, bool silent = false) + => LoadFromStream(new FileStream(binaryFile, FileMode.Open, FileAccess.Read, FileShare.Read), + new MemoryStream(File.ReadAllBytes(metadataFile)), + loadOptions, statusCallback, silent); + + // Load from a binary stream and metadata stream + // Must be a seekable stream otherwise we catch a System.IO.NotSupportedException + public static List LoadFromStream(Stream binaryStream, MemoryStream metadataStream, LoadOptions loadOptions = null, EventHandler statusCallback = null, bool silent = false) { + + // Silent operation if requested + var stdout = Console.Out; + if (silent) + Console.SetOut(new StreamWriter(Stream.Null)); + + // Load the metadata file + Metadata metadata; + try { + metadata = Metadata.FromStream(metadataStream, statusCallback); + } + catch (Exception ex) { + Console.Error.WriteLine(ex.Message); + Console.SetOut(stdout); + return null; + } + + Console.WriteLine("Detected metadata version " + metadata.Version); + + // Load the il2cpp code file (try all available file formats) + IFileFormatStream stream; + try { + stream = FileFormatStream.Load(binaryStream, loadOptions, statusCallback); + + if (stream == null) + throw new InvalidOperationException("Unsupported executable file format"); + } + catch (Exception ex) { + Console.Error.WriteLine(ex.Message); + Console.SetOut(stdout); + return null; + } + + // Multi-image binaries may contain more than one Il2Cpp image + var inspectors = LoadFromStream(stream, metadata, statusCallback); + + Console.SetOut(stdout); + + return inspectors; + } + + public static List LoadFromStream(IFileFormatStream stream, Metadata metadata, EventHandler statusCallback = null) { + + var processors = new List(); + foreach (var image in stream.Images) { + Console.WriteLine("Container format: " + image.Format); + Console.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness); + Console.WriteLine("Architecture word size: {0}-bit", image.Bits); + Console.WriteLine("Instruction set: " + image.Arch); + Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset); + + // Architecture-agnostic load attempt + try { + if (Il2CppBinary.Load(image, metadata, statusCallback) is Il2CppBinary binary) { + Console.WriteLine("IL2CPP binary version " + image.Version); + + processors.Add(new Il2CppInspector(binary, metadata)); + } + else { + Console.Error.WriteLine("Could not process IL2CPP image. This may mean the binary file is packed, encrypted or obfuscated in a way Il2CppInspector cannot process, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data."); + Console.Error.WriteLine("Please ensure you have downloaded and installed the latest set of core plugins and try again. Check the binary file in a disassembler to ensure that it is a valid IL2CPP binary before submitting a bug report!"); + } + } + // Unknown architecture + catch (NotImplementedException ex) { + Console.Error.WriteLine(ex.Message); + } + } + + // Plugin hook LoadPipelineEnding + PluginHooks.LoadPipelineEnding(processors); + + return processors; + } + + // Savers + public void SaveMetadataToFile(string pathname) => Metadata.SaveToFile(pathname); + public void SaveBinaryToFile(string pathname) => Binary.SaveToFile(pathname); + #endregion + } +} diff --git a/Il2CppInspector.Common/Reflection/TypeModel.cs b/Il2CppInspector.Common/Reflection/TypeModel.cs index 40d7847..19b460d 100644 --- a/Il2CppInspector.Common/Reflection/TypeModel.cs +++ b/Il2CppInspector.Common/Reflection/TypeModel.cs @@ -259,7 +259,7 @@ namespace Il2CppInspector.Reflection } // Create a reference type if necessary - return typeRef.byref ? underlyingType.MakeByRefType() : underlyingType; + return typeRef.ByRef(Package.Version) ? underlyingType.MakeByRefType() : underlyingType; } // Basic primitive types are specified via a flag value diff --git a/Il2CppInspector.Common/Utils/BlobReader.cs b/Il2CppInspector.Common/Utils/BlobReader.cs new file mode 100644 index 0000000..2cbb5fa --- /dev/null +++ b/Il2CppInspector.Common/Utils/BlobReader.cs @@ -0,0 +1,137 @@ +using NoisyCowStudios.Bin2Object; +using System.Text; +using System; + +namespace Il2CppInspector.Utils; + +public static class BlobReader +{ + public static object GetConstantValueFromBlob(Il2CppInspector inspector, Il2CppTypeEnum type, BinaryObjectStream blob) + { + const byte kArrayTypeWithDifferentElements = 1; + + object value = null; + + switch (type) + { + case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: + value = blob.ReadBoolean(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U1: + case Il2CppTypeEnum.IL2CPP_TYPE_I1: + value = blob.ReadByte(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: + // UTF-8 character assumed + value = BitConverter.ToChar(blob.ReadBytes(2), 0); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U2: + value = blob.ReadUInt16(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_I2: + value = blob.ReadInt16(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U4: + value = blob.Version >= 29 + ? blob.ReadCompressedUInt32() + : blob.ReadUInt32(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_I4: + value = blob.Version >= 29 + ? blob.ReadCompressedInt32() + : blob.ReadInt32(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_U8: + value = blob.ReadUInt64(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_I8: + value = blob.ReadInt64(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_R4: + value = blob.ReadSingle(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_R8: + value = blob.ReadDouble(); + break; + case Il2CppTypeEnum.IL2CPP_TYPE_STRING: + var uiLen = blob.Version >= 29 + ? blob.ReadCompressedInt32() + : blob.ReadInt32(); + + if (uiLen != -1) + value = Encoding.UTF8.GetString(blob.ReadBytes(uiLen)); + + break; + case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: + var length = blob.Version >= 29 + ? blob.ReadCompressedInt32() + : blob.ReadInt32(); + + if (length == -1) + break; + + var arrayElementType = ReadEncodedTypeEnum(inspector, blob, out var arrayElementDef); + var arrayElementsAreDifferent = blob.ReadByte(); + + if (arrayElementsAreDifferent == kArrayTypeWithDifferentElements) + { + var array = new ConstantBlobArrayElement[length]; + for (int i = 0; i < length; i++) + { + var elementType = ReadEncodedTypeEnum(inspector, blob, out var elementTypeDef); + array[i] = new ConstantBlobArrayElement(elementTypeDef, GetConstantValueFromBlob(inspector, elementType, blob)); + } + + value = new ConstantBlobArray(arrayElementDef, array); + } + else + { + var array = new object[length]; + for (int i = 0; i < length; i++) + { + array[i] = GetConstantValueFromBlob(inspector, arrayElementType, blob); + } + + value = new ConstantBlobArray(arrayElementDef, array); + } + + break; + + case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: + case Il2CppTypeEnum.IL2CPP_TYPE_OBJECT: + case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: + break; + case Il2CppTypeEnum.IL2CPP_TYPE_IL2CPP_TYPE_INDEX: + var index = blob.ReadCompressedInt32(); + if (index != -1) + value = inspector.TypeReferences[index]; + + break; + + + } + + return value; + } + + public static Il2CppTypeEnum ReadEncodedTypeEnum(Il2CppInspector inspector, BinaryObjectStream blob, + out Il2CppTypeDefinition enumType) + { + enumType = null; + + var typeEnum = (Il2CppTypeEnum)blob.ReadByte(); + if (typeEnum == Il2CppTypeEnum.IL2CPP_TYPE_ENUM) + { + var typeIndex = blob.ReadCompressedInt32(); + enumType = inspector.TypeDefinitions[typeIndex]; + typeEnum = inspector.TypeReferences[enumType.byvalTypeIndex].type; + } + // This technically also handles SZARRAY (System.Array) and all others by just returning their system type + + return typeEnum; + } + + public record ConstantBlobArray(Il2CppTypeDefinition ArrayTypeDef, object[] Elements); + + public record ConstantBlobArrayElement(Il2CppTypeDefinition TypeDef, object value); +} \ No newline at end of file