/* Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com All rights reserved. */ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using NoisyCowStudios.Bin2Object; namespace Il2CppInspector { public class Metadata : BinaryObjectReader { public Il2CppGlobalMetadataHeader Header; public Il2CppImageDefinition[] Images { get; } public Il2CppTypeDefinition[] Types { get; } public Il2CppMethodDefinition[] Methods { get; } public Il2CppParameterDefinition[] Params { get; } public Il2CppFieldDefinition[] Fields { get; } public Il2CppFieldDefaultValue[] FieldDefaultValues { get; } public Il2CppPropertyDefinition[] Properties { get; } public Il2CppEventDefinition[] Events { get; } public int[] InterfaceUsageIndices { get; } public int[] NestedTypeIndices { get; } public Dictionary Strings { get; } = new Dictionary(); public Metadata(Stream stream) : base(stream) { // Read magic bytes if (ReadUInt32() != 0xFAB11BAF) { throw new InvalidOperationException("ERROR: Metadata file supplied is not valid metadata file."); } // Set object versioning for Bin2Object from metadata version Version = ReadInt32(); // Rewind and read metadata header in full Header = ReadObject(0); if (Version < 16 || Version > 24) { throw new InvalidOperationException($"ERROR: Metadata file supplied is not a supported version ({Header.version})."); } // 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, 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. var realHeaderLength = Header.stringLiteralOffset; if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { if (Version == 24.0) { Version = 24.2; Header = ReadObject(0); } } if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { throw new InvalidOperationException("ERROR: 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 Images = ReadArray(Header.imagesOffset, Header.imagesCount / Sizeof(typeof(Il2CppImageDefinition))); // 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 >= 19 && Images.Any(x => x.token != 1)) if (Version == 24.0) { Version = 24.1; // No need to re-read the header, it's the same for both sub-versions Images = ReadArray(Header.imagesOffset, Header.imagesCount / Sizeof(typeof(Il2CppImageDefinition))); if (Images.Any(x => x.token != 1)) throw new InvalidOperationException("ERROR: Could not verify the integrity of the metadata file image list"); } Types = ReadArray(Header.typeDefinitionsOffset, Header.typeDefinitionsCount / Sizeof(typeof(Il2CppTypeDefinition))); Methods = ReadArray(Header.methodsOffset, Header.methodsCount / Sizeof(typeof(Il2CppMethodDefinition))); Params = ReadArray(Header.parametersOffset, Header.parametersCount / Sizeof(typeof(Il2CppParameterDefinition))); Fields = ReadArray(Header.fieldsOffset, Header.fieldsCount / Sizeof(typeof(Il2CppFieldDefinition))); FieldDefaultValues = ReadArray(Header.fieldDefaultValuesOffset, Header.fieldDefaultValuesCount / Sizeof(typeof(Il2CppFieldDefaultValue))); Properties = ReadArray(Header.propertiesOffset, Header.propertiesCount / Sizeof(typeof(Il2CppPropertyDefinition))); Events = ReadArray(Header.eventsOffset, Header.eventsCount / Sizeof(typeof(Il2CppEventDefinition))); InterfaceUsageIndices = ReadArray(Header.interfacesOffset, Header.interfacesCount / sizeof(int)); NestedTypeIndices = ReadArray(Header.nestedTypesOffset, Header.nestedTypesCount / sizeof(int)); // TODO: ParameterDefaultValue, GenericParameters, ParameterConstraints, GenericContainers, MetadataUsage, CustomAttributes // Get all string literals Position = Header.stringOffset; while (Position < Header.stringOffset + Header.stringCount) Strings.Add((int)Position - Header.stringOffset, ReadNullTerminatedString()); } private int Sizeof(Type type) { int size = 0; foreach (var i in type.GetTypeInfo().GetFields()) { // Only process fields for our selected object versioning var versionAttr = i.GetCustomAttribute(false); if (versionAttr != null) { if (versionAttr.Min != -1 && versionAttr.Min > Version) continue; if (versionAttr.Max != -1 && versionAttr.Max < Version) continue; } if (i.FieldType == typeof(int) || i.FieldType == typeof(uint)) size += 4; if (i.FieldType == typeof(short) || i.FieldType == typeof(ushort)) size += 2; } return size; } } }