From 5c2e06daee70b7c6f07206d7c3695edf23e5a6bf Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Tue, 4 Aug 2020 02:32:16 +0200 Subject: [PATCH] IDA: Refactor to use universal script and JSON metadata --- Il2CppInspector.CLI/Program.cs | 4 +- .../Outputs/IDAPythonScript.cs | 239 +++--------------- .../ScriptResources/shared-preamble.py | 2 + Il2CppTests/TestRunner.cs | 9 +- 4 files changed, 39 insertions(+), 215 deletions(-) diff --git a/Il2CppInspector.CLI/Program.cs b/Il2CppInspector.CLI/Program.cs index b591fcd..ac1dbf1 100644 --- a/Il2CppInspector.CLI/Program.cs +++ b/Il2CppInspector.CLI/Program.cs @@ -271,7 +271,9 @@ namespace Il2CppInspector.CLI // IDA Python script output using (new Benchmark("Generate IDAPython script")) { - new IDAPythonScript(appModel).WriteScriptToFile(options.PythonOutFile, Path.Combine(options.CppOutPath, "appdata/il2cpp-types.h")); + new IDAPythonScript(appModel).WriteScriptToFile(options.PythonOutFile, + Path.Combine(options.CppOutPath, "appdata/il2cpp-types.h"), + options.JsonOutPath); } } diff --git a/Il2CppInspector.Common/Outputs/IDAPythonScript.cs b/Il2CppInspector.Common/Outputs/IDAPythonScript.cs index 8557164..ca745bd 100755 --- a/Il2CppInspector.Common/Outputs/IDAPythonScript.cs +++ b/Il2CppInspector.Common/Outputs/IDAPythonScript.cs @@ -3,13 +3,9 @@ // Copyright 2020 Robert Xiao - https://robertxiao.ca/ // All rights reserved -using System; -using System.Collections.Generic; using System.Linq; using System.IO; -using System.Text; using Il2CppInspector.Reflection; -using Il2CppInspector.Cpp; using Il2CppInspector.Model; namespace Il2CppInspector.Outputs @@ -17,11 +13,10 @@ namespace Il2CppInspector.Outputs public class IDAPythonScript { private readonly AppModel model; - private StreamWriter writer; public IDAPythonScript(AppModel model) => this.model = model; - public void WriteScriptToFile(string outputFile, string existingTypeHeaderFIle = null) { + public void WriteScriptToFile(string outputFile, string existingTypeHeaderFIle = null, string existingJsonMetadataFile = null) { // Write types file first if it hasn't been specified var typeHeaderFile = Path.Combine(Path.GetDirectoryName(outputFile), Path.GetFileNameWithoutExtension(outputFile) + ".h"); @@ -31,220 +26,44 @@ namespace Il2CppInspector.Outputs else typeHeaderFile = existingTypeHeaderFIle; - var typeHeaderRelativePath = Path.GetRelativePath(Path.GetDirectoryName(Path.GetFullPath(outputFile)), - Path.GetDirectoryName(Path.GetFullPath(typeHeaderFile))) - + Path.DirectorySeparatorChar - + Path.GetFileName(typeHeaderFile); + var typeHeaderRelativePath = getRelativePath(outputFile, typeHeaderFile); - using var fs = new FileStream(outputFile, FileMode.Create); - writer = new StreamWriter(fs, Encoding.ASCII); + // Write JSON metadata if it hasn't been specified + var jsonMetadataFile = Path.Combine(Path.GetDirectoryName(outputFile), Path.GetFileNameWithoutExtension(outputFile) + ".json"); - writeLine("# Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty"); - writeLine("# Target Unity version: " + model.UnityHeaders); - writeLine("print('Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty')"); - writeSectionHeader("Preamble"); - writePreamble(); + if (string.IsNullOrEmpty(existingJsonMetadataFile)) + writeJsonMetadata(jsonMetadataFile); + else + jsonMetadataFile = existingJsonMetadataFile; - writeSectionHeader("Types"); - writeLine( -@"original_macros = ida_typeinf.get_c_macros() -ida_typeinf.set_c_macros(original_macros + "";_IDA_=1"") -idc.parse_decls(""" + typeHeaderRelativePath.ToEscapedString() + @""", idc.PT_FILE) -ida_typeinf.set_c_macros(original_macros)"); + var jsonMetadataRelativePath = getRelativePath(outputFile, jsonMetadataFile); - writeMethods(); + // TODO: Replace with target selector + var targetApi = "IDA"; - writeSectionHeader("String literals"); - writeStringLiterals(); + var ns = typeof(IDAPythonScript).Namespace + ".ScriptResources"; + var scripts = ResourceHelper.GetNamesForNamespace(ns); + var preamble = ResourceHelper.GetText(scripts.First(s => s == ns + ".shared-preamble.py")); + var main = ResourceHelper.GetText(scripts.First(s => s == ns + ".shared-main.py")); + var api = ResourceHelper.GetText(scripts.First(s => s == $"{ns}.{targetApi.ToLower()}-api.py")); - writeUsages(); + var script = string.Join("\n", new [] { preamble, api, main }) + .Replace("%SCRIPTFILENAME%", Path.GetFileName(outputFile)) + .Replace("%TYPE_HEADER_RELATIVE_PATH%", typeHeaderRelativePath.ToEscapedString()) + .Replace("%JSON_METADATA_RELATIVE_PATH%", jsonMetadataRelativePath.ToEscapedString()) + .Replace("%TARGET_UNITY_VERSION%", model.UnityHeaders.ToString()); - writeSectionHeader("Function boundaries"); - writeFunctions(); - - writeSectionHeader("IL2CPP Metadata"); - writeMetadata(); - - writeLine("print('Script execution complete.')"); - writer.Close(); + File.WriteAllText(outputFile, script); } - private void writePreamble() { - writeLine( - @"import idaapi + private void writeTypes(string typeHeaderFile) => new CppScaffolding(model).WriteTypes(typeHeaderFile); -def SetName(addr, name): - ret = idc.set_name(addr, name, SN_NOWARN | SN_NOCHECK) - if ret == 0: - new_name = name + '_' + str(addr) - ret = idc.set_name(addr, new_name, SN_NOWARN | SN_NOCHECK) + private void writeJsonMetadata(string jsonMetadataFile) => new JSONMetadata(model).Write(jsonMetadataFile); -def MakeFunction(start): - ida_funcs.add_func(start) - -def SetFunctionType(addr, sig): - SetType(addr, sig) - -def SetType(addr, type): - ret = idc.SetType(addr, type) - if ret is None: - print('SetType(0x%x, %r) failed!' % (addr, type)) - -def SetComment(addr, text): - idc.set_cmt(addr, text, 1) - -def SetHeaderComment(addr, text): - SetComment(addr, text)"); - } - - private void writeTypes(string typeHeaderFile) { - var cpp = new CppScaffolding(model); - cpp.WriteTypes(typeHeaderFile); - } - - private void writeMethods() { - writeSectionHeader("Method definitions"); - writeMethods(model.GetMethodGroup("types_from_methods")); - - writeSectionHeader("Constructed generic methods"); - writeMethods(model.GetMethodGroup("types_from_generic_methods")); - - writeSectionHeader("Custom attributes generators"); - foreach (var method in model.ILModel.AttributesByIndices.Values) { - writeTypedFunctionName(method.VirtualAddress.Value.Start, method.Signature, method.Name); - } - - writeSectionHeader("Method.Invoke thunks"); - foreach (var method in model.ILModel.MethodInvokers.Where(m => m != null)) { - writeTypedFunctionName(method.VirtualAddress.Start, method.GetSignature(model.UnityVersion), method.Name); - } - } - - private void writeMethods(IEnumerable methods) { - foreach (var method in methods) { - writeTypedFunctionName(method.MethodCodeAddress, method.CppFnPtrType.ToSignatureString(), method.CppFnPtrType.Name); - writeHeaderComment(method.MethodCodeAddress, method.Method); - } - } - - private void writeStringLiterals() { - // For version < 19 - if (model.StringIndexesAreOrdinals) { - var enumSrc = new StringBuilder(); - enumSrc.Append("enum StringLiteralIndex {\n"); - foreach (var str in model.Strings) - enumSrc.Append($" STRINGLITERAL_{str.Key}_{stringToIdentifier(str.Value)},\n"); - enumSrc.Append("};\n"); - - writeDecls(enumSrc.ToString()); - return; - } - - // For version >= 19 - var stringType = model.CppTypeCollection.GetType("String *"); - - foreach (var str in model.Strings) { - writeTypedName(str.Key, stringType.ToString(), $"StringLiteral_{stringToIdentifier(str.Value)}"); - writeComment(str.Key, str.Value); - } - } - - private void writeUsages() { - // Definition and reference addresses for all types from metadata usages - writeSectionHeader("Il2CppClass (TypeInfo) and Il2CppType (TypeRef) pointers"); - - foreach (var type in model.Types.Values) { - // A type may have no addresses, for example an unreferenced array type - - if (type.TypeClassAddress != 0xffffffff_ffffffff) { - writeTypedName(type.TypeClassAddress, $"struct {type.Name}__Class *", $"{type.Name}__TypeInfo"); - writeComment(type.TypeClassAddress, type.ILType.CSharpName); - } - - if (type.TypeRefPtrAddress != 0xffffffff_ffffffff) { - // A generic type definition does not have any direct C++ types, but may have a reference - writeTypedName(type.TypeRefPtrAddress, "struct Il2CppType *", $"{type.Name}__TypeRef"); - writeComment(type.TypeRefPtrAddress, type.ILType.CSharpName); - } - } - - // Metedata usage methods - writeSectionHeader("MethodInfo pointers"); - - foreach (var method in model.Methods.Values.Where(m => m.MethodInfoPtrAddress != 0xffffffff_ffffffff)) { - writeTypedName(method.MethodInfoPtrAddress, "struct MethodInfo *", $"{method.CppFnPtrType.Name}__MethodInfo"); - writeComment(method.MethodInfoPtrAddress, method.Method); - } - } - - private void writeFunctions() { - foreach (var func in model.Package.FunctionAddresses) - writeLine($"MakeFunction({func.Key.ToAddressString()})"); - } - - private void writeMetadata() { - var binary = model.Package.Binary; - - // TODO: In the future, add struct definitions/fields, data ranges and the entire IL2CPP metadata tree - - writeTypedName(binary.CodeRegistrationPointer, "struct Il2CppCodeRegistration", "g_CodeRegistration"); - writeTypedName(binary.MetadataRegistrationPointer, "struct Il2CppMetadataRegistration", "g_MetadataRegistration"); - - if (model.Package.Version >= 24.2) - writeTypedName(binary.CodeRegistration.pcodeGenModules, - $"struct Il2CppCodeGenModule *[{binary.CodeRegistration.codeGenModulesCount}]", "g_CodeGenModules"); - - foreach (var ptr in binary.CodeGenModulePointers) - writeTypedName(ptr.Value, "struct Il2CppCodeGenModule", $"g_{ptr.Key.Replace(".dll", "")}CodeGenModule"); - - // This will be zero if we found the structs from the symbol table - if (binary.RegistrationFunctionPointer != 0) - writeName(binary.RegistrationFunctionPointer, "__GLOBAL__sub_I_Il2CppCodeRegistration.cpp"); - } - - private void writeSectionHeader(string sectionName) { - writeLine(""); - writeLine($"# SECTION: {sectionName}"); - writeLine($"# -----------------------------"); - writeLine($"print('Processing {sectionName}')"); - writeLine(""); - } - - private void writeDecls(string decls) { - var lines = decls.Replace("\r", "").Split('\n'); - var cleanLines = lines.Select((s) => s.ToEscapedString()); - var declString = string.Join('\n', cleanLines); - if (declString != "") - writeLine("idc.parse_decls('''" + declString + "''')"); - } - - private void writeName(ulong address, string name) { - writeLine($"SetName({address.ToAddressString()}, r'{name.ToEscapedString()}')"); - } - - private void writeTypedName(ulong address, string type, string name) { - writeName(address, name); - writeLine($"SetType({address.ToAddressString()}, r'{type.ToEscapedString()}')"); - } - - private void writeTypedFunctionName(ulong address, string type, string name) { - writeName(address, name); - writeLine($"SetFunctionType({address.ToAddressString()}, r'{type.ToEscapedString()}')"); - } - - private void writeComment(ulong address, object comment) { - writeLine($"SetComment({address.ToAddressString()}, r'{comment.ToString().ToEscapedString()}')"); - } - - private void writeHeaderComment(ulong address, object comment) { - writeLine($"SetHeaderComment({address.ToAddressString()}, r'{comment.ToString().ToEscapedString()}')"); - } - - private void writeLine(string line) => writer.WriteLine(line); - - private static string stringToIdentifier(string str) { - str = str.Substring(0, Math.Min(32, str.Length)); - return str.ToCIdentifier(); - } + private string getRelativePath(string from, string to) => + Path.GetRelativePath(Path.GetDirectoryName(Path.GetFullPath(from)), + Path.GetDirectoryName(Path.GetFullPath(to))) + + Path.DirectorySeparatorChar + + Path.GetFileName(to); } } diff --git a/Il2CppInspector.Common/Outputs/ScriptResources/shared-preamble.py b/Il2CppInspector.Common/Outputs/ScriptResources/shared-preamble.py index a77c19d..efcfb05 100644 --- a/Il2CppInspector.Common/Outputs/ScriptResources/shared-preamble.py +++ b/Il2CppInspector.Common/Outputs/ScriptResources/shared-preamble.py @@ -1,3 +1,5 @@ # Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty +# Target Unity version: %TARGET_UNITY_VERSION% + import json import os diff --git a/Il2CppTests/TestRunner.cs b/Il2CppTests/TestRunner.cs index 370d0e4..9bbd36c 100644 --- a/Il2CppTests/TestRunner.cs +++ b/Il2CppTests/TestRunner.cs @@ -57,12 +57,13 @@ namespace Il2CppInspector new JSONMetadata(appModel) .Write(testPath + $@"\test-result{nameSuffix}.json"); - new IDAPythonScript(appModel) - .WriteScriptToFile(testPath + $@"\test-ida-result{nameSuffix}.py", - testPath + $@"\test-cpp-result{nameSuffix}\appdata\il2cpp-types.h"); - new CppScaffolding(appModel) .Write(testPath + $@"\test-cpp-result{nameSuffix}"); + + new IDAPythonScript(appModel) + .WriteScriptToFile(testPath + $@"\test-ida-result{nameSuffix}.py", + testPath + $@"\test-cpp-result{nameSuffix}\appdata\il2cpp-types.h", + testPath + $@"\test-result{nameSuffix}.json"); } // Compare test results with expected results