diff --git a/Il2CppInspector.Common/Il2CppInspector.csproj b/Il2CppInspector.Common/Il2CppInspector.csproj index adffaa5..3d897c5 100644 --- a/Il2CppInspector.Common/Il2CppInspector.csproj +++ b/Il2CppInspector.Common/Il2CppInspector.csproj @@ -19,6 +19,7 @@ + diff --git a/Il2CppInspector.Common/Outputs/JSONMetadata.cs b/Il2CppInspector.Common/Outputs/JSONMetadata.cs new file mode 100644 index 0000000..20c53c4 --- /dev/null +++ b/Il2CppInspector.Common/Outputs/JSONMetadata.cs @@ -0,0 +1,198 @@ +// Copyright (c) 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty +// All rights reserved + +using System; +using System.Collections.Generic; +using System.Linq; +using System.IO; +using System.Text.Json; +using Il2CppInspector.Reflection; +using Il2CppInspector.Model; + +namespace Il2CppInspector.Outputs +{ + public class JSONMetadata + { + private readonly AppModel model; + private Utf8JsonWriter writer; + + public JSONMetadata(AppModel model) => this.model = model; + + public void Write(string outputFile) { + + using var fs = new FileStream(outputFile, FileMode.Create); + writer = new Utf8JsonWriter(fs, options: new JsonWriterOptions { Indented = true }); + writer.WriteStartObject(); + + // Output address map of everything in the binary that we recognize + writeObject( + "addressMap", + () => { + writeMethods(); + writeStringLiterals(); + writeUsages(); + writeFunctions(); + writeMetadata(); + }, + "Address map of methods, internal functions, type pointers and string literals in the binary file" + ); + + writer.WriteEndObject(); + writer.Dispose(); + } + + private void writeMethods() { + writeArray("methodDefinitions", () => writeMethods(model.GetMethodGroup("types_from_methods")), "Method definitions"); + writeArray("constructedGenericMethods", () => writeMethods(model.GetMethodGroup("types_from_generic_methods")), "Constructed generic methods"); + + writeArray("customAttributesGenerators", () => { + foreach (var method in model.ILModel.AttributesByIndices.Values) { + writeObject(() => writeTypedFunctionName(method.VirtualAddress.Value.Start, method.Signature, method.Name)); + } + }, "Custom attributes generators"); + + writeArray("methodInvokers", () => { + foreach (var method in model.ILModel.MethodInvokers.Where(m => m != null)) { + writeObject(() => writeTypedFunctionName(method.VirtualAddress.Start, method.GetSignature(model.UnityVersion), method.Name)); + } + }, "Method.Invoke thunks"); + } + + private void writeMethods(IEnumerable methods) { + foreach (var method in methods) { + writeObject(() => { + writeTypedFunctionName(method.MethodCodeAddress, method.CppFnPtrType.ToSignatureString(), method.CppFnPtrType.Name); + writeDotNetSignature(method.Method); + }); + } + } + + private void writeStringLiterals() { + writeObject("stringLiterals", () => { + foreach (var str in model.Strings) + if (model.StringIndexesAreOrdinals) + writer.WriteNumber(str.Value, str.Key); + else + writer.WriteString(str.Key.ToAddressString(), str.Value); + }, "String literals"); + } + + private void writeUsages() { + // TypeInfo addresses for all types from metadata usages + writeArray("typeInfoPointers", + () => { + foreach (var type in model.Types.Values) { + // A type may have no addresses, for example an unreferenced array type + + if (type.TypeClassAddress != 0xffffffff_ffffffff) { + writeObject(() => { + writeTypedName(type.TypeClassAddress, $"struct {type.Name}__Class *", $"{type.Name}__TypeInfo"); + writeDotNetTypeName(type.ILType); + }); + } + } + }, "Il2CppClass (TypeInfo) pointers"); + + // Reference addresses for all types from metadata usages + writeArray("typeRefPointers", + () => { + foreach (var type in model.Types.Values) { + if (type.TypeRefPtrAddress != 0xffffffff_ffffffff) { + writeObject(() => { + // A generic type definition does not have any direct C++ types, but may have a reference + writeName(type.TypeRefPtrAddress, $"{type.Name}__TypeRef"); + writeDotNetTypeName(type.ILType); + }); + } + } + }, "Il2CppType (TypeRef) pointers"); + + // Metedata usage methods + writeArray("methodInfoPointers", + () => { + foreach (var method in model.Methods.Values.Where(m => m.MethodInfoPtrAddress != 0xffffffff_ffffffff)) { + writeObject(() => { + writeName(method.MethodInfoPtrAddress, $"{method.CppFnPtrType.Name}__MethodInfo"); + writeDotNetSignature(method.Method); + }); + } + }, "MethodInfo pointers"); + } + + private void writeFunctions() { + writeArray("functionAddresses", () => { + foreach (var func in model.Package.FunctionAddresses) + writer.WriteStringValue(func.Key.ToAddressString()); + }, "Function boundaries"); + } + + private void writeMetadata() { + var binary = model.Package.Binary; + + // TODO: In the future, add struct definitions/fields, data ranges and the entire IL2CPP metadata tree + writeArray("typeMetadata", () => { + writeObject(() => writeTypedName(binary.CodeRegistrationPointer, "struct Il2CppCodeRegistration", "g_CodeRegistration")); + writeObject(() => writeTypedName(binary.MetadataRegistrationPointer, "struct Il2CppMetadataRegistration", "g_MetadataRegistration")); + + if (model.Package.Version >= 24.2) + writeObject(() => writeTypedName(binary.CodeRegistration.pcodeGenModules, + $"struct Il2CppCodeGenModule *[{binary.CodeRegistration.codeGenModulesCount}]", "g_CodeGenModules")); + + foreach (var ptr in binary.CodeGenModulePointers) + writeObject(() => writeTypedName(ptr.Value, "struct Il2CppCodeGenModule", $"g_{ptr.Key.Replace(".dll", "")}CodeGenModule")); + }, "IL2CPP Type Metadata"); + + writeArray("functionMetadata", () => { + // This will be zero if we found the structs from the symbol table + if (binary.RegistrationFunctionPointer != 0) + writeObject(() => writeTypedFunctionName(binary.RegistrationFunctionPointer, + "void il2cpp_codegen_register(const Il2CppCodeRegistration* const codeRegistration, const Il2CppMetadataRegistration* const metadataRegistration, const Il2CppCodeGenOptions* const codeGenOptions)", + "il2cpp_codegen_register")); + }, "IL2CPP Function Metadata"); + } + + private void writeObject(Action objectWriter) => writeObject(null, objectWriter); + + private void writeObject(string name, Action objectWriter, string description = null) { + if (description != null) + writer.WriteCommentValue(" " + description + " "); + if (name != null) + writer.WriteStartObject(name); + else + writer.WriteStartObject(); + objectWriter(); + writer.WriteEndObject(); + } + + private void writeArray(string name, Action arrayWriter, string description = null) { + writer.WriteStartArray(name); + if (description != null) + writer.WriteCommentValue(" " + description + " "); + arrayWriter(); + writer.WriteEndArray(); + } + + private void writeName(ulong address, string name) { + writer.WriteString("virtualAddress", address.ToAddressString()); + writer.WriteString("name", name.ToEscapedString()); + } + + private void writeTypedName(ulong address, string type, string name) { + writeName(address, name); + writer.WriteString("type", type.ToEscapedString()); + } + + private void writeTypedFunctionName(ulong address, string type, string name) { + writeName(address, name); + writer.WriteString("signature", type.ToEscapedString()); + } + + private void writeDotNetSignature(MethodBase method) { + writer.WriteString("dotNetSignature", method.ToString().ToEscapedString()); + } + + private void writeDotNetTypeName(TypeInfo type) { + writer.WriteString("dotNetType", type.CSharpName); + } + } +}