IDA: Refactor to use universal script and JSON metadata

This commit is contained in:
Katy Coe
2020-08-04 02:32:16 +02:00
parent dea29751ef
commit 5c2e06daee
4 changed files with 39 additions and 215 deletions

View File

@@ -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);
}
}

View File

@@ -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)))
var typeHeaderRelativePath = getRelativePath(outputFile, typeHeaderFile);
// Write JSON metadata if it hasn't been specified
var jsonMetadataFile = Path.Combine(Path.GetDirectoryName(outputFile), Path.GetFileNameWithoutExtension(outputFile) + ".json");
if (string.IsNullOrEmpty(existingJsonMetadataFile))
writeJsonMetadata(jsonMetadataFile);
else
jsonMetadataFile = existingJsonMetadataFile;
var jsonMetadataRelativePath = getRelativePath(outputFile, jsonMetadataFile);
// TODO: Replace with target selector
var targetApi = "IDA";
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"));
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());
File.WriteAllText(outputFile, script);
}
private void writeTypes(string typeHeaderFile) => new CppScaffolding(model).WriteTypes(typeHeaderFile);
private void writeJsonMetadata(string jsonMetadataFile) => new JSONMetadata(model).Write(jsonMetadataFile);
private string getRelativePath(string from, string to) =>
Path.GetRelativePath(Path.GetDirectoryName(Path.GetFullPath(from)),
Path.GetDirectoryName(Path.GetFullPath(to)))
+ Path.DirectorySeparatorChar
+ Path.GetFileName(typeHeaderFile);
using var fs = new FileStream(outputFile, FileMode.Create);
writer = new StreamWriter(fs, Encoding.ASCII);
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();
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)");
writeMethods();
writeSectionHeader("String literals");
writeStringLiterals();
writeUsages();
writeSectionHeader("Function boundaries");
writeFunctions();
writeSectionHeader("IL2CPP Metadata");
writeMetadata();
writeLine("print('Script execution complete.')");
writer.Close();
}
private void writePreamble() {
writeLine(
@"import idaapi
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)
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<AppMethod> 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();
}
+ Path.GetFileName(to);
}
}

View File

@@ -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

View File

@@ -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