/* 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.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 Dictionary Strings { get; } = new Dictionary(); public Metadata(Stream stream) : base(stream) { // Read magic bytes if (ReadUInt32() != 0xFAB11BAF) { throw new Exception("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 < 21 || Version > 24) { throw new Exception($"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 0x108 (24.0) or 0x110 (24.1) 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 the IL2CPP metadata sub-version used. var realHeaderLength = Header.stringLiteralOffset; if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { if (Version == 24.0) { Version = 24.1; Header = ReadObject(0); } } if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) { throw new Exception("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 foreach (var i in Images) if (i.token != 1) throw new Exception("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.propertiesOffset / Sizeof(typeof(Il2CppPropertyDefinition))); Events = ReadArray(Header.eventsOffset, Header.eventsOffset / Sizeof(typeof(Il2CppEventDefinition))); InterfaceUsageIndices = ReadArray(Header.interfacesOffset, Header.interfacesCount / 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; } } }