/* Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty All rights reserved. */ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; using Il2CppInspector.Next; using Il2CppInspector.Next.Metadata; using NoisyCowStudios.Bin2Object; using VersionedSerialization; namespace Il2CppInspector { public class Metadata : BinaryObjectStreamReader { public Il2CppGlobalMetadataHeader Header { get; set; } public ImmutableArray Assemblies { get; set; } public ImmutableArray Images { get; set; } public ImmutableArray Types { get; set; } public ImmutableArray Methods { get; set; } public ImmutableArray Params { get; set; } public ImmutableArray Fields { get; set; } public ImmutableArray FieldDefaultValues { get; set; } public ImmutableArray ParameterDefaultValues { get; set; } public ImmutableArray Properties { get; set; } public ImmutableArray Events { get; set; } public ImmutableArray GenericContainers { get; set; } public ImmutableArray GenericParameters { get; set; } public ImmutableArray AttributeTypeRanges { get; set; } public ImmutableArray AttributeDataRanges { get; set; } public ImmutableArray InterfaceOffsets { get; set; } public ImmutableArray MetadataUsageLists { get; set; } public ImmutableArray MetadataUsagePairs { get; set; } public ImmutableArray FieldRefs { get; set; } public ImmutableArray InterfaceUsageIndices { get; set; } public ImmutableArray NestedTypeIndices { get; set; } public ImmutableArray AttributeTypeIndices { get; set; } public ImmutableArray GenericConstraintIndices { get; set; } public ImmutableArray VTableMethodIndices { get; set; } public string[] StringLiterals { get; set; } public Dictionary Strings { get; private set; } = new Dictionary(); // Set if something in the metadata has been modified / decrypted public bool IsModified { get; private set; } = false; // Status update callback private EventHandler OnStatusUpdate { get; set; } private void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status); // Initialize metadata object from a stream public static Metadata FromStream(MemoryStream stream, EventHandler statusCallback = null) { // TODO: This should really be placed before the Metadata object is created, // but for now this ensures it is called regardless of which client is in use PluginHooks.LoadPipelineStarting(); var metadata = new Metadata(statusCallback); stream.Position = 0; stream.CopyTo(metadata); metadata.Position = 0; metadata.Initialize(); return metadata; } private Metadata(EventHandler statusCallback = null) : base() => OnStatusUpdate = statusCallback; private void Initialize() { // Pre-processing hook var pluginResult = PluginHooks.PreProcessMetadata(this); IsModified = pluginResult.IsStreamModified; StatusUpdate("Processing metadata"); // Read metadata header Header = ReadVersionedObject(0); // Check for correct magic bytes if (!Header.SanityValid) { throw new InvalidOperationException("The supplied metadata file is not valid."); } // Set object versioning for Bin2Object from metadata version Version = new StructVersion(Header.Version); if (Version < MetadataVersions.V160 || Version > MetadataVersions.V310) { throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version})."); } // Rewind and read metadata header with the correct version settings Header = ReadVersionedObject(0); // Sanity checking // Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata always writes the metadata information in the same order it appears in the header, // with each block always coming directly after the previous block, 4-byte aligned. We can use this to check the integrity of the data and // detect sub-versions. // For metadata v24.0, the header can either be either 0x110 (24.0, 24.1) or 0x108 (24.2) bytes long. Since 'stringLiteralOffset' is the first thing // in the header after the sanity and version fields, and since it will always point directly to the first byte after the end of the header, // we can use this value to determine the actual header length and therefore narrow down the metadata version to 24.0/24.1 or 24.2. if (!pluginResult.SkipValidation) { var realHeaderLength = Header.StringLiteralOffset; if (realHeaderLength != Sizeof()) { if (Version == MetadataVersions.V240) { Version = MetadataVersions.V242; Header = ReadVersionedObject(0); } } if (realHeaderLength != Sizeof()) { throw new InvalidOperationException("Could not verify the integrity of the metadata file or accurately identify the metadata sub-version"); } } // Load all the relevant metadata using offsets provided in the header if (Version >= MetadataVersions.V160) Images = ReadVersionedObjectArray(Header.ImagesOffset, Header.ImagesSize / Sizeof()); // As an additional sanity check, all images in the metadata should have Mono.Cecil.MetadataToken == 1 // In metadata v24.1, two extra fields were added which will cause the below test to fail. // In that case, we can then adjust the version number and reload // Tokens were introduced in v19 - we don't bother testing earlier versions if (Version >= MetadataVersions.V190 && Images.Any(x => x.Token != 1)) if (Version == MetadataVersions.V240) { Version = MetadataVersions.V241; // No need to re-read the header, it's the same for both sub-versions Images = ReadVersionedObjectArray(Header.ImagesOffset, Header.ImagesSize / Sizeof()); if (Images.Any(x => x.Token != 1)) throw new InvalidOperationException("Could not verify the integrity of the metadata file image list"); } Types = ReadVersionedObjectArray(Header.TypeDefinitionsOffset, Header.TypeDefinitionsSize / Sizeof()); Methods = ReadVersionedObjectArray(Header.MethodsOffset, Header.MethodsSize / Sizeof()); Params = ReadVersionedObjectArray(Header.ParametersOffset, Header.ParametersSize / Sizeof()); Fields = ReadVersionedObjectArray(Header.FieldsOffset, Header.FieldsSize / Sizeof()); FieldDefaultValues = ReadVersionedObjectArray(Header.FieldDefaultValuesOffset, Header.FieldDefaultValuesSize / Sizeof()); Properties = ReadVersionedObjectArray(Header.PropertiesOffset, Header.PropertiesSize / Sizeof()); Events = ReadVersionedObjectArray(Header.EventsOffset, Header.EventsSize / Sizeof()); InterfaceUsageIndices = ReadPrimitiveArray(Header.InterfacesOffset, Header.InterfacesSize / sizeof(int)); NestedTypeIndices = ReadPrimitiveArray(Header.NestedTypesOffset, Header.NestedTypesSize / sizeof(int)); GenericContainers = ReadVersionedObjectArray(Header.GenericContainersOffset, Header.GenericContainersSize / Sizeof()); GenericParameters = ReadVersionedObjectArray(Header.GenericParametersOffset, Header.GenericParametersSize / Sizeof()); GenericConstraintIndices = ReadPrimitiveArray(Header.GenericParameterConstraintsOffset, Header.GenericParameterConstraintsSize / sizeof(int)); InterfaceOffsets = ReadVersionedObjectArray(Header.InterfaceOffsetsOffset, Header.InterfaceOffsetsSize / Sizeof()); VTableMethodIndices = ReadPrimitiveArray(Header.VTableMethodsOffset, Header.VTableMethodsSize / sizeof(uint)); if (Version >= MetadataVersions.V160) { // In v24.4 hashValueIndex was removed from Il2CppAssemblyNameDefinition, which is a field in Il2CppAssemblyDefinition // The number of images and assemblies should be the same. If they are not, we deduce that we are using v24.4 // Note the version comparison matches both 24.2 and 24.3 here since 24.3 is tested for during binary loading var assemblyCount = Header.AssembliesSize / Sizeof(); var changedAssemblyDefStruct = false; if ((Version == MetadataVersions.V241 || Version == MetadataVersions.V242 || Version == MetadataVersions.V243) && assemblyCount < Images.Length) { if (Version == MetadataVersions.V241) changedAssemblyDefStruct = true; Version = MetadataVersions.V244; } Assemblies = ReadVersionedObjectArray(Header.AssembliesOffset, Images.Length); if (changedAssemblyDefStruct) Version = MetadataVersions.V241; ParameterDefaultValues = ReadVersionedObjectArray(Header.ParameterDefaultValuesOffset, Header.ParameterDefaultValuesSize / Sizeof()); } if (Version >= MetadataVersions.V190 && Version < MetadataVersions.V270) { MetadataUsageLists = ReadVersionedObjectArray(Header.MetadataUsageListsOffset, Header.MetadataUsageListsCount / Sizeof()); MetadataUsagePairs = ReadVersionedObjectArray(Header.MetadataUsagePairsOffset, Header.MetadataUsagePairsCount / Sizeof()); } if (Version >= MetadataVersions.V190) { FieldRefs = ReadVersionedObjectArray(Header.FieldRefsOffset, Header.FieldRefsSize / Sizeof()); } if (Version >= MetadataVersions.V210 && Version < MetadataVersions.V290) { AttributeTypeIndices = ReadPrimitiveArray(Header.AttributesTypesOffset, Header.AttributesTypesCount / sizeof(int)); AttributeTypeRanges = ReadVersionedObjectArray(Header.AttributesInfoOffset, Header.AttributesInfoCount / Sizeof()); } if (Version >= MetadataVersions.V290) { AttributeDataRanges = ReadVersionedObjectArray(Header.AttributeDataRangeOffset, Header.AttributeDataRangeSize / Sizeof()); } if (Version is 29 or 31) { // 29.2/31.2 added a new isUnmanagedCallersOnly flag to Il2CppMethodDefinition. // This offsets all subsequent entries by one - we can detect this by checking the // top token byte (which should always be 0x06). if (Methods.Length >= 2) { var secondToken = Methods[1].token; if (secondToken >> 24 != 0x6) { Version += 0.2; Methods = ReadArray(Header.methodsOffset, Header.methodsCount / Sizeof(typeof(Il2CppMethodDefinition))); } } } // Get all metadata strings var pluginGetStringsResult = PluginHooks.GetStrings(this); if (pluginGetStringsResult.IsDataModified && !pluginGetStringsResult.IsInvalid) Strings = pluginGetStringsResult.Strings; else { Position = Header.StringOffset; while (Position < Header.StringOffset + Header.StringSize) Strings.Add((int) Position - Header.StringOffset, ReadNullTerminatedString()); } // Get all string literals var pluginGetStringLiteralsResult = PluginHooks.GetStringLiterals(this); if (pluginGetStringLiteralsResult.IsDataModified) StringLiterals = pluginGetStringLiteralsResult.StringLiterals.ToArray(); else { var stringLiteralList = ReadVersionedObjectArray(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof()); StringLiterals = new string[stringLiteralList.Length]; for (var i = 0; i < stringLiteralList.Length; i++) StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length); } // Post-processing hook IsModified |= PluginHooks.PostProcessMetadata(this).IsStreamModified; } // Save metadata to file, overwriting if necessary public void SaveToFile(string pathname) { Position = 0; using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write)) CopyTo(outFile); } public int Sizeof() where T : IReadable => T.Size(Version, Is32Bit); } }