Struct reading and disassembly script overhaul, various misc. loading fixes, bump to .NET 9 (#13)
* Bump projects to .net 9 and update nugets * add VersionedSerialization + source generator * migrate versioning to StructVersion class, add handling/detection for 29.2/31.2 * add new struct definitions * rename serialization methods and add BinaryObjectStreamReader for interop * Rework metadata struct loading to use new struct versioning * move 29/31.1/.2 to use tags (-2022,-2023) instead of minor versions * fix metadata usage validity checks * rework code registration offsetting a bit and add second 29/31.1 condition * tweak .1 condition (again) * 29/31.2 was a psyop * also remove 29.2 from the readme * remove loading of packed dlls - this was a very unsafe feature * support auto-recovering type indices from type handles fixes loading of memory-dumped v29+ libraries since those replacee their class indices on load with a pointer to the corresponding type * support loading PEs without an export table * also read UnresolvedVirtualCallCount on regular v31 * Disable plugin loading for now * Overhaul disassembler script + add Binary Ninja target (#12) * Overhaul diassembler scripts: - No longer defines top level functions - Split into three classes: StatusHandler (like before), DisassemblerInterface (for interfacing with the used program API), ScriptContext (for definiting general functions that use the disassembler interface) - Add type annotations to all class methods and remove 2.7 compatibility stuff (Ghidra now supports Python 3 so this is unnecessary anymore) - Disassembler backends are now responsible for launching metadata/script processing, to better support disassembler differences - String handling is back in the base ScriptContext class, disassembler interfaces opt into the fake string segment creation and fall back to the old method if it isn't supported * Add Binary Ninja disassembler script backend This uses the new backend-controlled execution to launch metadata processing on a background thread to keep the ui responsive * make binary ninja script use own _BINARYNINJA_ define and add define helpers to header * Update README to account for new script and binary ninja backend * implement fake string segment functions for binary ninja but don't advertise support * also cache API function types in binary ninja backend * fix ida script and disable folders again * Fix metadata usage issues caused by it being a value type now * make TryMapVATR overrideable and implement it for ELFs * Make field offset reading use TryMapVATR to reduce exceptions * Fix NRE in Assembly ctor on < v24.2 * Update actions workflow to produce cross-platform CLI binaries, update readme to reflect .net 9 changes * workflow: only restore packages for projects that are being built * workflow: tweak caching and fix gui compilation * workflow: remove double .zip in CLI artifact name * 29/31.2 don't actually exist, this logic is not needed
This commit is contained in:
@@ -12,6 +12,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using dnlib.DotNet;
|
||||
using dnlib.DotNet.Emit;
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Reflection;
|
||||
|
||||
namespace Il2CppInspector.Outputs
|
||||
@@ -186,7 +187,7 @@ namespace Il2CppInspector.Outputs
|
||||
};
|
||||
|
||||
if (mType.IsExplicitLayout || mType.IsSequentialLayout)
|
||||
mType.ClassLayout = new ClassLayoutUser(1, (uint)type.Sizes.nativeSize);
|
||||
mType.ClassLayout = new ClassLayoutUser(1, (uint)type.Sizes.NativeSize);
|
||||
|
||||
// Add nested types
|
||||
foreach (var nestedType in type.DeclaredNestedTypes)
|
||||
@@ -241,7 +242,7 @@ namespace Il2CppInspector.Outputs
|
||||
AddMethod(module, mType, method);
|
||||
|
||||
// Add token attribute
|
||||
if (type.Definition != null)
|
||||
if (type.Definition.IsValid)
|
||||
mType.AddAttribute(module, tokenAttribute, ("Token", $"0x{type.MetadataToken:X8}"));
|
||||
|
||||
// Add custom attribute attributes
|
||||
@@ -269,7 +270,7 @@ namespace Il2CppInspector.Outputs
|
||||
if (field.HasFieldRVA) {
|
||||
// Attempt to get field size
|
||||
|
||||
var fieldSize = field.FieldType.Sizes.nativeSize;
|
||||
var fieldSize = field.FieldType.Sizes.NativeSize;
|
||||
var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, fieldSize);
|
||||
|
||||
mField.InitialValue = preview;
|
||||
@@ -310,7 +311,7 @@ namespace Il2CppInspector.Outputs
|
||||
|
||||
// Add token attribute
|
||||
// Generic properties and constructed properties (from disperate get/set methods) have no definition
|
||||
if (prop.Definition != null)
|
||||
if (prop.Definition.IsValid)
|
||||
mProp.AddAttribute(module, tokenAttribute, ("Token", $"0x{prop.MetadataToken:X8}"));
|
||||
|
||||
// Add custom attribute attributes
|
||||
@@ -432,8 +433,8 @@ namespace Il2CppInspector.Outputs
|
||||
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))),
|
||||
("VA", method.VirtualAddress.Value.Start.ToAddressString())
|
||||
};
|
||||
if (method.Definition.slot != ushort.MaxValue)
|
||||
args.Add(("Slot", method.Definition.slot.ToString()));
|
||||
if (method.Definition.Slot != ushort.MaxValue)
|
||||
args.Add(("Slot", method.Definition.Slot.ToString()));
|
||||
|
||||
mMethod.AddAttribute(module, addressAttribute, args.ToArray());
|
||||
}
|
||||
@@ -591,7 +592,7 @@ namespace Il2CppInspector.Outputs
|
||||
// Create folder for DLLs
|
||||
Directory.CreateDirectory(outputPath);
|
||||
|
||||
if (model.Package.Version >= 29)
|
||||
if (model.Package.Version >= MetadataVersions.V290)
|
||||
{
|
||||
// We can now apply all attributes directly.
|
||||
directApplyAttributes = model.TypesByDefinitionIndex
|
||||
@@ -648,7 +649,7 @@ namespace Il2CppInspector.Outputs
|
||||
AddCustomAttribute(module, module.Assembly, ca);
|
||||
|
||||
// Add token attributes
|
||||
module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.token:X8}"));
|
||||
module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.Token:X8}"));
|
||||
module.Assembly.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.MetadataToken:X8}"));
|
||||
|
||||
if (types.TryGetValue(module, out var shallowTypes))
|
||||
|
||||
@@ -353,7 +353,7 @@ namespace Il2CppInspector.Outputs
|
||||
foreach (var asm in assemblies) {
|
||||
text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName}");
|
||||
if (!SuppressMetadata)
|
||||
text.Append($" - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}");
|
||||
text.Append($" - Types {asm.ImageDefinition.TypeStart}-{asm.ImageDefinition.TypeStart + asm.ImageDefinition.TypeCount - 1}");
|
||||
text.AppendLine();
|
||||
|
||||
// Assembly-level attributes
|
||||
@@ -426,7 +426,7 @@ namespace Il2CppInspector.Outputs
|
||||
sb.Append($" // Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}");
|
||||
// For static array initializers, output metadata address and preview
|
||||
if (field.HasFieldRVA && !SuppressMetadata) {
|
||||
var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.nativeSize);
|
||||
var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.NativeSize);
|
||||
sb.Append($" // Static value (base64): {Convert.ToBase64String(preview)} - Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}");
|
||||
}
|
||||
sb.Append("\n");
|
||||
|
||||
@@ -38,14 +38,15 @@ namespace Il2CppInspector.Outputs
|
||||
using var fs = new FileStream(typeHeaderFile, FileMode.Create);
|
||||
_writer = new StreamWriter(fs, Encoding.ASCII);
|
||||
|
||||
const string decompilerIfDef = "#if !defined(_GHIDRA_) && !defined(_IDA_) && !defined(_IDACLANG_)";
|
||||
|
||||
using (_writer)
|
||||
{
|
||||
writeHeader();
|
||||
|
||||
// Write primitive type definitions for when we're not including other headers
|
||||
writeCode($"""
|
||||
#define IS_LIBCLANG_DECOMPILER (defined(_IDACLANG_) || defined(_BINARYNINJA_))
|
||||
#define IS_DECOMPILER (defined(_GHIDRA_) || defined(_IDA_) || IS_LIBCLANG_DECOMPILER)
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDA_)
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
@@ -57,7 +58,7 @@ namespace Il2CppInspector.Outputs
|
||||
typedef __int64 int64_t;
|
||||
#endif
|
||||
|
||||
#ifdef _IDACLANG_
|
||||
#if IS_LIBCLANG_DECOMPILER
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
@@ -68,13 +69,13 @@ namespace Il2CppInspector.Outputs
|
||||
typedef long int64_t;
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDACLANG_)
|
||||
#if defined(_GHIDRA_) || IS_LIBCLANG_DECOMPILER
|
||||
typedef int{_model.Package.BinaryImage.Bits}_t intptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t uintptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t size_t;
|
||||
#endif
|
||||
|
||||
{decompilerIfDef}
|
||||
#if !IS_DECOMPILER
|
||||
#define _CPLUSPLUS_
|
||||
#endif
|
||||
""");
|
||||
@@ -114,7 +115,7 @@ namespace Il2CppInspector.Outputs
|
||||
}
|
||||
|
||||
// C does not support namespaces
|
||||
writeCode($"{decompilerIfDef}");
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("namespace app {");
|
||||
writeCode("#endif");
|
||||
writeLine("");
|
||||
@@ -124,7 +125,7 @@ namespace Il2CppInspector.Outputs
|
||||
writeTypesForGroup("Application types from usages", "types_from_usages");
|
||||
writeTypesForGroup("Application unused value types", "unused_concrete_types");
|
||||
|
||||
writeCode($"{decompilerIfDef}");
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("}");
|
||||
writeCode("#endif");
|
||||
}
|
||||
@@ -248,7 +249,7 @@ namespace Il2CppInspector.Outputs
|
||||
using (_writer)
|
||||
{
|
||||
writeHeader();
|
||||
writeCode($"#define __IL2CPP_METADATA_VERSION {_model.Package.Version * 10:F0}");
|
||||
writeCode($"#define __IL2CPP_METADATA_VERSION {_model.Package.Version.Major * 10 + _model.Package.Version.Minor * 10:F0}");
|
||||
}
|
||||
|
||||
// Write boilerplate code
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.IO;
|
||||
using System.Text.Json;
|
||||
using Il2CppInspector.Reflection;
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Next;
|
||||
|
||||
namespace Il2CppInspector.Outputs
|
||||
{
|
||||
@@ -182,8 +183,8 @@ namespace Il2CppInspector.Outputs
|
||||
|
||||
// TODO: In the future, add data ranges for the entire IL2CPP metadata tree
|
||||
writeArray("arrayMetadata", () => {
|
||||
if (model.Package.Version >= 24.2) {
|
||||
writeObject(() => writeTypedArray(binary.CodeRegistration.pcodeGenModules, binary.Modules.Count, "struct Il2CppCodeGenModule *", "g_CodeGenModules"));
|
||||
if (model.Package.Version >= MetadataVersions.V242) {
|
||||
writeObject(() => writeTypedArray(binary.CodeRegistration.CodeGenModules, binary.Modules.Count, "struct Il2CppCodeGenModule *", "g_CodeGenModules"));
|
||||
}
|
||||
}, "IL2CPP Array Metadata");
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ namespace Il2CppInspector.Outputs
|
||||
public static IEnumerable<string> GetAvailableTargets() {
|
||||
var ns = typeof(PythonScript).Namespace + ".ScriptResources.Targets";
|
||||
var res = ResourceHelper.GetNamesForNamespace(ns);
|
||||
return res.Select(s => Path.GetFileNameWithoutExtension(s.Substring(ns.Length + 1))).OrderBy(s => s);
|
||||
return res
|
||||
.Select(s => Path.GetFileNameWithoutExtension(s[(ns.Length + 1)..]))
|
||||
.OrderBy(s => s);
|
||||
}
|
||||
|
||||
// Output script file
|
||||
@@ -52,12 +54,11 @@ namespace Il2CppInspector.Outputs
|
||||
|
||||
var jsonMetadataRelativePath = getRelativePath(outputFile, jsonMetadataFile);
|
||||
|
||||
var ns = typeof(PythonScript).Namespace + ".ScriptResources";
|
||||
var preamble = ResourceHelper.GetText(ns + ".shared-preamble.py");
|
||||
var main = ResourceHelper.GetText(ns + ".shared-main.py");
|
||||
var api = ResourceHelper.GetText($"{ns}.Targets.{target}.py");
|
||||
var ns = $"{typeof(PythonScript).Namespace}.ScriptResources";
|
||||
var baseScipt = ResourceHelper.GetText($"{ns}.shared_base.py");
|
||||
var impl = ResourceHelper.GetText($"{ns}.Targets.{target}.py");
|
||||
|
||||
var script = string.Join("\n", new [] { preamble, api, main })
|
||||
var script = string.Join("\n", baseScipt, impl)
|
||||
.Replace("%SCRIPTFILENAME%", Path.GetFileName(outputFile))
|
||||
.Replace("%TYPE_HEADER_RELATIVE_PATH%", typeHeaderRelativePath.ToEscapedString())
|
||||
.Replace("%JSON_METADATA_RELATIVE_PATH%", jsonMetadataRelativePath.ToEscapedString())
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
from binaryninja import *
|
||||
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# import sys
|
||||
# from datetime import datetime
|
||||
#except:
|
||||
# pass
|
||||
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
# this is implemented,
|
||||
# however the write API does not seem to work properly here (possibly a bug),
|
||||
# so this is disabled for now
|
||||
supports_fake_string_segment: bool = False
|
||||
|
||||
_status: BaseStatusHandler
|
||||
|
||||
_view: BinaryView
|
||||
_undo_id: str
|
||||
_components: dict[str, Component]
|
||||
_type_cache: dict[str, Type]
|
||||
_function_type_cache: dict[str, Type]
|
||||
|
||||
_address_size: int
|
||||
_endianness: Literal["little", "big"]
|
||||
|
||||
def __init__(self, status: BaseStatusHandler):
|
||||
self._status = status
|
||||
|
||||
def _get_or_create_type(self, type: str) -> Type:
|
||||
if type.startswith("struct "):
|
||||
type = type[len("struct "):]
|
||||
elif type.startswith("class "):
|
||||
type = type[len("class "):]
|
||||
|
||||
if type in self._type_cache:
|
||||
return self._type_cache[type]
|
||||
|
||||
if type.endswith("*"):
|
||||
base_type = self._get_or_create_type(type[:-1].strip())
|
||||
|
||||
parsed = PointerType.create(self._view.arch, base_type) # type: ignore
|
||||
else:
|
||||
parsed = self._view.get_type_by_name(type)
|
||||
if parsed is None:
|
||||
parsed, errors = self._view.parse_type_string(type)
|
||||
|
||||
self._type_cache[type] = parsed
|
||||
return parsed
|
||||
|
||||
def get_script_directory(self) -> str:
|
||||
return CURRENT_PATH
|
||||
|
||||
def on_start(self):
|
||||
self._view = bv # type: ignore
|
||||
self._undo_id = self._view.begin_undo_actions()
|
||||
self._view.set_analysis_hold(True)
|
||||
self._components = {}
|
||||
self._type_cache = {}
|
||||
self._function_type_cache = {}
|
||||
|
||||
self._address_size = self._view.address_size
|
||||
self._endianness = "little" if self._view.endianness == Endianness.LittleEndian else "big"
|
||||
|
||||
self._status.update_step("Parsing header")
|
||||
|
||||
with open(os.path.join(self.get_script_directory(), "il2cpp.h"), "r") as f:
|
||||
parsed_types, errors = TypeParser.default.parse_types_from_source(
|
||||
f.read(),
|
||||
"il2cpp.h",
|
||||
self._view.platform if self._view.platform is not None else Platform["windows-x86_64"],
|
||||
self._view,
|
||||
[
|
||||
"--target=x86_64-pc-linux",
|
||||
"-x", "c++",
|
||||
"-D_BINARYNINJA_=1"
|
||||
]
|
||||
)
|
||||
|
||||
if parsed_types is None:
|
||||
log_error("Failed to import header")
|
||||
log_error(errors)
|
||||
return
|
||||
|
||||
self._status.update_step("Importing header types", len(parsed_types.types))
|
||||
|
||||
def import_progress_func(progress: int, total: int):
|
||||
self._status.update_progress(1)
|
||||
return True
|
||||
|
||||
self._view.define_user_types([(x.name, x.type) for x in parsed_types.types], import_progress_func)
|
||||
|
||||
def on_finish(self):
|
||||
self._view.commit_undo_actions(self._undo_id)
|
||||
self._view.set_analysis_hold(False)
|
||||
self._view.update_analysis()
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
if self._view.get_function_at(address) is not None:
|
||||
return
|
||||
|
||||
self._view.create_user_function(address)
|
||||
|
||||
def define_data_array(self, address: int, type: str, count: int):
|
||||
parsed_type = self._get_or_create_type(type)
|
||||
array_type = ArrayType.create(parsed_type, count)
|
||||
var = self._view.get_data_var_at(address)
|
||||
if var is None:
|
||||
self._view.define_user_data_var(address, array_type)
|
||||
else:
|
||||
var.type = array_type
|
||||
|
||||
def set_data_type(self, address: int, type: str):
|
||||
var = self._view.get_data_var_at(address)
|
||||
dtype = self._get_or_create_type(type)
|
||||
if var is None:
|
||||
self._view.define_user_data_var(address, dtype)
|
||||
else:
|
||||
var.type = dtype
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
if type in self._function_type_cache:
|
||||
function.type = self._function_type_cache[type] # type: ignore
|
||||
else:
|
||||
#log_info(f"skipping function type setting for {address}, {type}")
|
||||
#pass
|
||||
function.type = type.replace("this", "`this`")
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
self._view.set_comment_at(address, cmt)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
function.comment = cmt
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
var = self._view.get_data_var_at(address)
|
||||
if var is None:
|
||||
return
|
||||
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
var.name = get_qualified_name(demangled)
|
||||
else:
|
||||
var.name = name
|
||||
|
||||
def set_function_name(self, address: int, name: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
function.name = get_qualified_name(demangled)
|
||||
#function.type = type - this does not work due to the generated types not being namespaced. :(
|
||||
else:
|
||||
function.name = name
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
self._view.add_user_data_ref(from_address, to_address)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
self._view.define_user_type(None, type_def)
|
||||
|
||||
# optional
|
||||
def _get_or_create_component(self, name: str):
|
||||
if name in self._components:
|
||||
return self._components[name]
|
||||
|
||||
current = name
|
||||
if current.count("/") != 0:
|
||||
split_idx = current.rindex("/")
|
||||
parent, child = current[:split_idx], current[split_idx:]
|
||||
parent = self._get_or_create_component(name)
|
||||
component = self._view.create_component(child, parent)
|
||||
else:
|
||||
component = self._view.create_component(name)
|
||||
|
||||
self._components[name] = component
|
||||
return component
|
||||
|
||||
def add_function_to_group(self, address: int, group: str):
|
||||
return
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
self._get_or_create_component(group).add_function(function)
|
||||
|
||||
def cache_function_types(self, signatures: list[str]):
|
||||
function_sigs = set(signatures)
|
||||
if len(function_sigs) == 0:
|
||||
return
|
||||
|
||||
typestr = ";\n".join(function_sigs).replace("this", "_this") + ";"
|
||||
res = self._view.parse_types_from_string(typestr)
|
||||
for function_sig, function in zip(function_sigs, res.functions.values()): # type: ignore
|
||||
self._function_type_cache[function_sig] = function
|
||||
|
||||
# only required if supports_fake_string_segment == True
|
||||
def create_fake_segment(self, name: str, size: int) -> int:
|
||||
last_end_addr = self._view.mapped_address_ranges[-1].end
|
||||
if last_end_addr % 0x1000 != 0:
|
||||
last_end_addr += (0x1000 - (last_end_addr % 0x1000))
|
||||
|
||||
self._view.add_user_segment(last_end_addr, size, 0, 0, SegmentFlag.SegmentContainsData)
|
||||
self._view.add_user_section(name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics)
|
||||
return last_end_addr
|
||||
|
||||
def write_string(self, address: int, value: str):
|
||||
self._view.write(address, value.encode() + b"\x00")
|
||||
|
||||
def write_address(self, address: int, value: int):
|
||||
self._view.write(address, value.to_bytes(self._address_size, self._endianness))
|
||||
|
||||
|
||||
class BinaryNinjaStatusHandler(BaseStatusHandler):
|
||||
def __init__(self, thread: BackgroundTaskThread):
|
||||
self.step = "Initializing"
|
||||
self.max_items = 0
|
||||
self.current_items = 0
|
||||
self.start_time = datetime.now()
|
||||
self.step_start_time = self.start_time
|
||||
self.last_updated_time = datetime.min
|
||||
self._thread = thread
|
||||
|
||||
def initialize(self): pass
|
||||
|
||||
def update(self):
|
||||
if self.was_cancelled():
|
||||
raise RuntimeError("Cancelled script.")
|
||||
|
||||
current_time = datetime.now()
|
||||
if 0.5 > (current_time - self.last_updated_time).total_seconds():
|
||||
return
|
||||
|
||||
self.last_updated_time = current_time
|
||||
|
||||
step_time = current_time - self.step_start_time
|
||||
total_time = current_time - self.start_time
|
||||
self._thread.progress = f"Processing IL2CPP metadata: {self.step} ({self.current_items}/{self.max_items}), elapsed: {step_time} ({total_time})"
|
||||
|
||||
def update_step(self, step, max_items = 0):
|
||||
self.step = step
|
||||
self.max_items = max_items
|
||||
self.current_items = 0
|
||||
self.step_start_time = datetime.now()
|
||||
self.last_updated_time = datetime.min
|
||||
self.update()
|
||||
|
||||
def update_progress(self, new_progress = 1):
|
||||
self.current_items += new_progress
|
||||
self.update()
|
||||
|
||||
def was_cancelled(self): return False
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
# Entry point
|
||||
class Il2CppTask(BackgroundTaskThread):
|
||||
def __init__(self):
|
||||
BackgroundTaskThread.__init__(self, "Processing IL2CPP metadata...", False)
|
||||
|
||||
def run(self):
|
||||
status = BinaryNinjaStatusHandler(self)
|
||||
backend = BinaryNinjaDisassemblerInterface(status)
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
|
||||
Il2CppTask().start()
|
||||
@@ -1,106 +1,124 @@
|
||||
# Ghidra-specific implementation
|
||||
from ghidra.app.cmd.function import ApplyFunctionSignatureCmd
|
||||
from ghidra.app.script import GhidraScriptUtil
|
||||
from ghidra.app.util.cparser.C import CParserUtils
|
||||
from ghidra.program.model.data import ArrayDataType
|
||||
from ghidra.program.model.symbol import SourceType
|
||||
from ghidra.program.model.symbol import RefType
|
||||
from ghidra.app.cmd.label import DemanglerCmd
|
||||
|
||||
xrefs = currentProgram.getReferenceManager()
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# import sys
|
||||
# from datetime import datetime
|
||||
#except:
|
||||
# pass
|
||||
|
||||
def set_name(addr, name):
|
||||
if not name.startswith("_ZN"):
|
||||
createLabel(toAddr(addr), name, True)
|
||||
return
|
||||
cmd = DemanglerCmd(currentAddress.getAddress(hex(addr)), name)
|
||||
if not cmd.applyTo(currentProgram, monitor):
|
||||
print("Failed to apply demangled name to %s at %s due %s, falling back to mangled" % (name, hex(addr), cmd.getStatusMsg()))
|
||||
createLabel(toAddr(addr), name, True)
|
||||
class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
supports_fake_string_segment = False
|
||||
|
||||
def make_function(start, end = None):
|
||||
addr = toAddr(start)
|
||||
# Don't override existing functions
|
||||
fn = getFunctionAt(addr)
|
||||
if fn is None:
|
||||
# Create new function if none exists
|
||||
createFunction(addr, None)
|
||||
def get_script_directory(self) -> str:
|
||||
return getSourceFile().getParentFile().toString()
|
||||
|
||||
def make_array(addr, numItems, cppType):
|
||||
if cppType.startswith('struct '):
|
||||
cppType = cppType[7:]
|
||||
|
||||
t = getDataTypes(cppType)[0]
|
||||
a = ArrayDataType(t, numItems, t.getLength())
|
||||
addr = toAddr(addr)
|
||||
removeDataAt(addr)
|
||||
createData(addr, a)
|
||||
def on_start(self):
|
||||
self.xrefs = currentProgram.getReferenceManager()
|
||||
|
||||
def define_code(code):
|
||||
# Code declarations are not supported in Ghidra
|
||||
# This only affects string literals for metadata version < 19
|
||||
# TODO: Replace with creating a DataType for enums
|
||||
# Check that the user has parsed the C headers first
|
||||
if len(getDataTypes('Il2CppObject')) == 0:
|
||||
print('STOP! You must import the generated C header file (%TYPE_HEADER_RELATIVE_PATH%) before running this script.')
|
||||
print('See https://github.com/djkaty/Il2CppInspector/blob/master/README.md#adding-metadata-to-your-ghidra-workflow for instructions.')
|
||||
sys.exit()
|
||||
|
||||
# Ghidra sets the image base for ELF to 0x100000 for some reason
|
||||
# https://github.com/NationalSecurityAgency/ghidra/issues/1020
|
||||
# Make sure that the base address is 0
|
||||
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time
|
||||
# If 0 doesn't work for you, replace it with the base address from the output of the CLI or GUI
|
||||
if currentProgram.getExecutableFormat().endswith('(ELF)'):
|
||||
currentProgram.setImageBase(toAddr(0), True)
|
||||
|
||||
# Don't trigger decompiler
|
||||
setAnalysisOption(currentProgram, "Call Convention ID", "false")
|
||||
|
||||
def on_finish(self):
|
||||
pass
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
address = toAddr(address)
|
||||
# Don't override existing functions
|
||||
fn = getFunctionAt(address)
|
||||
if fn is None:
|
||||
# Create new function if none exists
|
||||
createFunction(address, None)
|
||||
|
||||
def define_data_array(self, address: int, type: str, count: int):
|
||||
if type.startswith('struct '):
|
||||
type = type[7:]
|
||||
|
||||
t = getDataTypes(type)[0]
|
||||
a = ArrayDataType(t, count, t.getLength())
|
||||
address = toAddr(address)
|
||||
removeDataAt(address)
|
||||
createData(address, a)
|
||||
|
||||
def set_data_type(self, address: int, type: str):
|
||||
if type.startswith('struct '):
|
||||
type = type[7:]
|
||||
|
||||
try:
|
||||
t = getDataTypes(type)[0]
|
||||
address = toAddr(address)
|
||||
removeDataAt(address)
|
||||
createData(address, t)
|
||||
except:
|
||||
print("Failed to set type: %s" % type)
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
make_function(address)
|
||||
typeSig = CParserUtils.parseSignature(None, currentProgram, type)
|
||||
ApplyFunctionSignatureCmd(toAddr(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
setEOLComment(toAddr(address), cmt)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
setPlateComment(toAddr(address), cmt)
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
address = toAddr(address)
|
||||
|
||||
if len(name) > 2000:
|
||||
print("Name length exceeds 2000 characters, skipping (%s)" % name)
|
||||
return
|
||||
|
||||
if not name.startswith("_ZN"):
|
||||
createLabel(address, name, True)
|
||||
return
|
||||
|
||||
cmd = DemanglerCmd(address, name)
|
||||
if not cmd.applyTo(currentProgram, monitor):
|
||||
print(f"Failed to apply demangled name to {name} at {address} due {cmd.getStatusMsg()}, falling back to mangled")
|
||||
createLabel(address, name, True)
|
||||
|
||||
def set_function_name(self, address: int, name: str):
|
||||
return self.set_data_name(address, name)
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
self.xrefs.addMemoryReference(toAddr(from_address), toAddr(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
# Code declarations are not supported in Ghidra
|
||||
# This only affects string literals for metadata version < 19
|
||||
# TODO: Replace with creating a DataType for enums
|
||||
pass
|
||||
|
||||
class GhidraStatusHandler(BaseStatusHandler):
|
||||
pass
|
||||
|
||||
def set_function_type(addr, sig):
|
||||
make_function(addr)
|
||||
typeSig = CParserUtils.parseSignature(None, currentProgram, sig)
|
||||
ApplyFunctionSignatureCmd(toAddr(addr), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
|
||||
|
||||
def set_type(addr, cppType):
|
||||
if cppType.startswith('struct '):
|
||||
cppType = cppType[7:]
|
||||
|
||||
try:
|
||||
t = getDataTypes(cppType)[0]
|
||||
addr = toAddr(addr)
|
||||
removeDataAt(addr)
|
||||
createData(addr, t)
|
||||
except:
|
||||
print("Failed to set type: %s" % cppType)
|
||||
|
||||
def set_comment(addr, text):
|
||||
setEOLComment(toAddr(addr), text)
|
||||
|
||||
def set_header_comment(addr, text):
|
||||
setPlateComment(toAddr(addr), text)
|
||||
|
||||
def script_prologue(status):
|
||||
# Check that the user has parsed the C headers first
|
||||
if len(getDataTypes('Il2CppObject')) == 0:
|
||||
print('STOP! You must import the generated C header file (%TYPE_HEADER_RELATIVE_PATH%) before running this script.')
|
||||
print('See https://github.com/djkaty/Il2CppInspector/blob/master/README.md#adding-metadata-to-your-ghidra-workflow for instructions.')
|
||||
sys.exit()
|
||||
|
||||
# Ghidra sets the image base for ELF to 0x100000 for some reason
|
||||
# https://github.com/NationalSecurityAgency/ghidra/issues/1020
|
||||
# Make sure that the base address is 0
|
||||
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time
|
||||
# If 0 doesn't work for you, replace it with the base address from the output of the CLI or GUI
|
||||
if currentProgram.getExecutableFormat().endswith('(ELF)'):
|
||||
currentProgram.setImageBase(toAddr(0), True)
|
||||
|
||||
# Don't trigger decompiler
|
||||
setAnalysisOption(currentProgram, "Call Convention ID", "false")
|
||||
|
||||
def get_script_directory(): return getSourceFile().getParentFile().toString()
|
||||
|
||||
def script_epilogue(status): pass
|
||||
def add_function_to_group(addr, group): pass
|
||||
def add_xref(addr, to):
|
||||
xrefs.addMemoryReference(currentAddress.getAddress(hex(addr)), currentAddress.getAddress(hex(to)), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
|
||||
def process_string_literals(status, data):
|
||||
for d in jsonData['stringLiterals']:
|
||||
define_string(d)
|
||||
|
||||
# I don't know how to make inline strings in Ghidra
|
||||
# Just revert back original impl
|
||||
addr = parse_address(d)
|
||||
set_name(addr, d['name'])
|
||||
set_type(addr, r'struct String *')
|
||||
set_comment(addr, d['string'])
|
||||
|
||||
status.update_progress()
|
||||
|
||||
class StatusHandler(BaseStatusHandler): pass
|
||||
status = GhidraStatusHandler()
|
||||
backend = GhidraDisassemblerInterface()
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
@@ -8,6 +8,8 @@ import ida_nalt
|
||||
import ida_ida
|
||||
import ida_ua
|
||||
import ida_segment
|
||||
import ida_funcs
|
||||
import ida_xref
|
||||
|
||||
try: # 7.7+
|
||||
import ida_srclang
|
||||
@@ -23,206 +25,205 @@ try:
|
||||
except ImportError:
|
||||
FOLDERS_AVAILABLE = False
|
||||
|
||||
cached_genflags = 0
|
||||
skip_make_function = False
|
||||
func_dirtree = None
|
||||
is_32_bit = False
|
||||
fake_segments_base = None
|
||||
|
||||
def script_prologue(status):
|
||||
global cached_genflags, skip_make_function, func_dirtree, is_32_bit, fake_segments_base
|
||||
# Disable autoanalysis
|
||||
cached_genflags = ida_ida.inf_get_genflags()
|
||||
ida_ida.inf_set_genflags(cached_genflags & ~ida_ida.INFFL_AUTO)
|
||||
|
||||
# Unload type libraries we know to cause issues - like the c++ linux one
|
||||
PLATFORMS = ["x86", "x64", "arm", "arm64"]
|
||||
PROBLEMATIC_TYPELIBS = ["gnulnx"]
|
||||
|
||||
for lib in PROBLEMATIC_TYPELIBS:
|
||||
for platform in PLATFORMS:
|
||||
ida_typeinf.del_til(f"{lib}_{platform}")
|
||||
|
||||
# Set name mangling to GCC 3.x and display demangled as default
|
||||
ida_ida.inf_set_demnames(ida_ida.DEMNAM_GCC3 | ida_ida.DEMNAM_NAME)
|
||||
|
||||
status.update_step('Processing Types')
|
||||
|
||||
if IDACLANG_AVAILABLE:
|
||||
header_path = os.path.join(get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%")
|
||||
ida_srclang.set_parser_argv("clang", "-target x86_64-pc-linux -x c++ -D_IDACLANG_=1") # -target required for 8.3+
|
||||
ida_srclang.parse_decls_with_parser("clang", None, header_path, True)
|
||||
else:
|
||||
original_macros = ida_typeinf.get_c_macros()
|
||||
ida_typeinf.set_c_macros(original_macros + ";_IDA_=1")
|
||||
ida_typeinf.idc_parse_types(os.path.join(get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%"), ida_typeinf.PT_FILE)
|
||||
ida_typeinf.set_c_macros(original_macros)
|
||||
|
||||
# Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow
|
||||
skip_make_function = ida_segment.get_segm_by_name(".pdata") is not None
|
||||
if skip_make_function:
|
||||
print(".pdata section found, skipping function boundaries")
|
||||
|
||||
if FOLDERS_AVAILABLE:
|
||||
func_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)
|
||||
|
||||
is_32_bit = ida_ida.inf_is_32bit_exactly()
|
||||
|
||||
def script_epilogue(status):
|
||||
# Reenable auto-analysis
|
||||
global cached_genflags
|
||||
ida_ida.inf_set_genflags(cached_genflags)
|
||||
|
||||
# Utility methods
|
||||
|
||||
def set_name(addr, name):
|
||||
ida_name.set_name(addr, name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
def make_function(start, end = None):
|
||||
global skip_make_function
|
||||
if skip_make_function:
|
||||
return
|
||||
|
||||
ida_bytes.del_items(start, ida_bytes.DELIT_SIMPLE, 12) # Undefine x bytes which should hopefully be enough for the first instruction
|
||||
ida_ua.create_insn(start) # Create instruction at start
|
||||
if not ida_funcs.add_func(start, end if end is not None else ida_idaapi.BADADDR): # This fails if the function doesn't start with an instruction
|
||||
print(f"failed to mark function {hex(start)}-{hex(end) if end is not None else '???'} as function")
|
||||
|
||||
TYPE_CACHE = {}
|
||||
|
||||
def get_type(typeName):
|
||||
if typeName not in TYPE_CACHE:
|
||||
info = ida_typeinf.idc_parse_decl(None, typeName, ida_typeinf.PT_RAWARGS)
|
||||
if info is None:
|
||||
print(f"Failed to create type {typeName}.")
|
||||
return None
|
||||
|
||||
TYPE_CACHE[typeName] = info[1:]
|
||||
|
||||
return TYPE_CACHE[typeName]
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# from datetime import datetime
|
||||
#except:
|
||||
# pass
|
||||
|
||||
TINFO_DEFINITE = 0x0001 # These only exist in idc for some reason, so we redefine it here
|
||||
DEFAULT_TIL: "til_t" = None # type: ignore
|
||||
|
||||
def set_type(addr, cppType):
|
||||
cppType += ';'
|
||||
class IDADisassemblerInterface(BaseDisassemblerInterface):
|
||||
supports_fake_string_segment = True
|
||||
|
||||
info = get_type(cppType)
|
||||
if info is None:
|
||||
return
|
||||
_status: BaseStatusHandler
|
||||
|
||||
if ida_typeinf.apply_type(None, info[0], info[1], addr, TINFO_DEFINITE) is None:
|
||||
print(f"set_type({hex(addr)}, {cppType}); failed!")
|
||||
|
||||
def set_function_type(addr, sig):
|
||||
set_type(addr, sig)
|
||||
|
||||
def make_array(addr, numItems, cppType):
|
||||
set_type(addr, cppType)
|
||||
|
||||
flags = ida_bytes.get_flags(addr)
|
||||
if ida_bytes.is_struct(flags):
|
||||
opinfo = ida_nalt.opinfo_t()
|
||||
ida_bytes.get_opcode(opinfo, addr, 0, flags)
|
||||
entrySize = ida_bytes.get_data_elsize(addr, flags, opinfo)
|
||||
tid = opinfo.tid
|
||||
else:
|
||||
entrySize = ida_bytes.get_item_size(addr)
|
||||
tid = ida_idaapi.BADADDR
|
||||
|
||||
ida_bytes.create_data(addr, flags, numItems * entrySize, tid)
|
||||
|
||||
def define_code(code):
|
||||
ida_typeinf.idc_parse_types(code)
|
||||
|
||||
def set_comment(addr, comment, repeatable = True):
|
||||
ida_bytes.set_cmt(addr, comment, repeatable)
|
||||
|
||||
def set_header_comment(addr, comment):
|
||||
func = ida_funcs.get_func(addr)
|
||||
if func is None:
|
||||
return
|
||||
|
||||
ida_funcs.set_func_cmt(func, comment, True)
|
||||
|
||||
def get_script_directory():
|
||||
return os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
folders = []
|
||||
def add_function_to_group(addr, group):
|
||||
global func_dirtree, folders
|
||||
return
|
||||
|
||||
if not FOLDERS_AVAILABLE:
|
||||
return
|
||||
|
||||
if group not in folders:
|
||||
folders.append(group)
|
||||
func_dirtree.mkdir(group)
|
||||
|
||||
name = ida_funcs.get_func_name(addr)
|
||||
func_dirtree.rename(name, f"{group}/{name}")
|
||||
|
||||
def add_xref(addr, to):
|
||||
ida_xref.add_dref(addr, to, ida_xref.XREF_USER | ida_xref.dr_I)
|
||||
|
||||
def write_string(addr, string):
|
||||
encoded_string = string.encode() + b'\x00'
|
||||
string_length = len(encoded_string)
|
||||
ida_bytes.put_bytes(addr, encoded_string)
|
||||
ida_bytes.create_strlit(addr, string_length, ida_nalt.STRTYPE_C)
|
||||
|
||||
def write_address(addr, value):
|
||||
global is_32_bit
|
||||
|
||||
if is_32_bit:
|
||||
ida_bytes.put_dword(addr, value)
|
||||
else:
|
||||
ida_bytes.put_qword(addr, value)
|
||||
|
||||
def create_fake_segment(name, size):
|
||||
global is_32_bit
|
||||
|
||||
start = ida_ida.inf_get_max_ea()
|
||||
end = start + size
|
||||
|
||||
ida_segment.add_segm(0, start, end, name, "DATA")
|
||||
segment = ida_segment.get_segm_by_name(name)
|
||||
segment.bitness = 1 if is_32_bit else 2
|
||||
segment.perm = ida_segment.SEGPERM_READ
|
||||
segment.update()
|
||||
|
||||
return start
|
||||
|
||||
def process_string_literals(status, data):
|
||||
total_string_length = 0
|
||||
for d in data['stringLiterals']:
|
||||
total_string_length += len(d["string"]) + 1
|
||||
_type_cache: dict
|
||||
_folders: list
|
||||
|
||||
aligned_length = total_string_length + (4096 - (total_string_length % 4096))
|
||||
segment_base = create_fake_segment(".fake_strings", aligned_length)
|
||||
_function_dirtree: "ida_dirtree.dirtree_t"
|
||||
_cached_genflags: int
|
||||
_skip_function_creation: bool
|
||||
_is_32_bit: bool
|
||||
_fake_segments_base: int
|
||||
|
||||
current_string_address = segment_base
|
||||
for d in data['stringLiterals']:
|
||||
define_string(d)
|
||||
def __init__(self, status: BaseStatusHandler):
|
||||
self._status = status
|
||||
|
||||
self._type_cache = {}
|
||||
self._folders = []
|
||||
|
||||
ref_addr = parse_address(d)
|
||||
write_string(current_string_address, d["string"])
|
||||
write_address(ref_addr, current_string_address)
|
||||
set_type(ref_addr, r'const char* const')
|
||||
self._cached_genflags = 0
|
||||
self._skip_function_creation = False
|
||||
self._is_32_bit = False
|
||||
self._fake_segments_base = 0
|
||||
|
||||
current_string_address += len(d["string"]) + 1
|
||||
status.update_progress()
|
||||
def _get_type(self, type: str):
|
||||
if type not in self._type_cache:
|
||||
info = ida_typeinf.idc_parse_decl(DEFAULT_TIL, type, ida_typeinf.PT_RAWARGS)
|
||||
if info is None:
|
||||
print(f"Failed to create type {type}.")
|
||||
return None
|
||||
|
||||
self._type_cache[type] = info[1:]
|
||||
|
||||
return self._type_cache[type]
|
||||
|
||||
def get_script_directory(self) -> str:
|
||||
return os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
def on_start(self):
|
||||
# Disable autoanalysis
|
||||
self._cached_genflags = ida_ida.inf_get_genflags()
|
||||
ida_ida.inf_set_genflags(self._cached_genflags & ~ida_ida.INFFL_AUTO)
|
||||
|
||||
# Unload type libraries we know to cause issues - like the c++ linux one
|
||||
PLATFORMS = ["x86", "x64", "arm", "arm64"]
|
||||
PROBLEMATIC_TYPELIBS = ["gnulnx"]
|
||||
|
||||
for lib in PROBLEMATIC_TYPELIBS:
|
||||
for platform in PLATFORMS:
|
||||
ida_typeinf.del_til(f"{lib}_{platform}")
|
||||
|
||||
# Set name mangling to GCC 3.x and display demangled as default
|
||||
ida_ida.inf_set_demnames(ida_ida.DEMNAM_GCC3 | ida_ida.DEMNAM_NAME)
|
||||
|
||||
self._status.update_step('Processing Types')
|
||||
|
||||
if IDACLANG_AVAILABLE:
|
||||
header_path = os.path.join(self.get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%")
|
||||
ida_srclang.set_parser_argv("clang", "-target x86_64-pc-linux -x c++ -D_IDACLANG_=1") # -target required for 8.3+
|
||||
ida_srclang.parse_decls_with_parser("clang", None, header_path, True)
|
||||
else:
|
||||
original_macros = ida_typeinf.get_c_macros()
|
||||
ida_typeinf.set_c_macros(original_macros + ";_IDA_=1")
|
||||
ida_typeinf.idc_parse_types(os.path.join(self.get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%"), ida_typeinf.PT_FILE)
|
||||
ida_typeinf.set_c_macros(original_macros)
|
||||
|
||||
# Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow
|
||||
skip_make_function = ida_segment.get_segm_by_name(".pdata") is not None
|
||||
if skip_make_function:
|
||||
print(".pdata section found, skipping function boundaries")
|
||||
|
||||
if FOLDERS_AVAILABLE:
|
||||
self._function_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)
|
||||
|
||||
self._is_32_bit = ida_ida.inf_is_32bit_exactly()
|
||||
|
||||
def on_finish(self):
|
||||
ida_ida.inf_set_genflags(self._cached_genflags)
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
if self._skip_function_creation:
|
||||
return
|
||||
|
||||
ida_bytes.del_items(address, ida_bytes.DELIT_SIMPLE, 12) # Undefine x bytes which should hopefully be enough for the first instruction
|
||||
ida_ua.create_insn(address) # Create instruction at start
|
||||
if not ida_funcs.add_func(address, end if end is not None else ida_idaapi.BADADDR): # This fails if the function doesn't start with an instruction
|
||||
print(f"failed to mark function {hex(address)}-{hex(end) if end is not None else '???'} as function")
|
||||
|
||||
def define_data_array(self, address: int, type: str, count: int):
|
||||
self.set_data_type(address, type)
|
||||
|
||||
flags = ida_bytes.get_flags(address)
|
||||
if ida_bytes.is_struct(flags):
|
||||
opinfo = ida_nalt.opinfo_t()
|
||||
ida_bytes.get_opinfo(opinfo, address, 0, flags)
|
||||
entrySize = ida_bytes.get_data_elsize(address, flags, opinfo)
|
||||
tid = opinfo.tid
|
||||
else:
|
||||
entrySize = ida_bytes.get_item_size(address)
|
||||
tid = ida_idaapi.BADADDR
|
||||
|
||||
ida_bytes.create_data(address, flags, count * entrySize, tid)
|
||||
|
||||
def set_data_type(self, address: int, type: str):
|
||||
type += ';'
|
||||
|
||||
info = self._get_type(type)
|
||||
if info is None:
|
||||
return
|
||||
|
||||
if ida_typeinf.apply_type(DEFAULT_TIL, info[0], info[1], address, TINFO_DEFINITE) is None:
|
||||
print(f"set_type({hex(address)}, {type}); failed!")
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
self.set_data_type(address, type)
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
ida_bytes.set_cmt(address, cmt, False)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
func = ida_funcs.get_func(address)
|
||||
if func is None:
|
||||
return
|
||||
|
||||
ida_funcs.set_func_cmt(func, cmt, True)
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
ida_name.set_name(address, name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK | ida_name.SN_FORCE)
|
||||
|
||||
def set_function_name(self, address: int, name: str):
|
||||
self.set_data_name(address, name)
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
ida_xref.add_dref(from_address, to_address, ida_xref.XREF_USER | ida_xref.dr_I)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
ida_typeinf.idc_parse_types(type_def, 0)
|
||||
|
||||
# optional
|
||||
def add_function_to_group(self, address: int, group: str):
|
||||
if not FOLDERS_AVAILABLE or True: # enable at your own risk - this is slow
|
||||
return
|
||||
|
||||
if group not in self._folders:
|
||||
self._folders.append(group)
|
||||
self._function_dirtree.mkdir(group)
|
||||
|
||||
name = ida_funcs.get_func_name(address)
|
||||
self._function_dirtree.rename(name, f"{group}/{name}")
|
||||
|
||||
# only required if supports_fake_string_segment == True
|
||||
def create_fake_segment(self, name: str, size: int) -> int:
|
||||
start = ida_ida.inf_get_max_ea()
|
||||
end = start + size
|
||||
|
||||
ida_segment.add_segm(0, start, end, name, "DATA")
|
||||
segment = ida_segment.get_segm_by_name(name)
|
||||
segment.bitness = 1 if self._is_32_bit else 2
|
||||
segment.perm = ida_segment.SEGPERM_READ
|
||||
segment.update()
|
||||
|
||||
return start
|
||||
|
||||
def write_string(self, address: int, value: str):
|
||||
encoded_string = value.encode() + b'\x00'
|
||||
string_length = len(encoded_string)
|
||||
ida_bytes.put_bytes(address, encoded_string)
|
||||
ida_bytes.create_strlit(address, string_length, ida_nalt.STRTYPE_C)
|
||||
|
||||
def write_address(self, address: int, value: int):
|
||||
if self._is_32_bit:
|
||||
ida_bytes.put_dword(address, value)
|
||||
else:
|
||||
ida_bytes.put_qword(address, value)
|
||||
|
||||
# Status handler
|
||||
|
||||
class StatusHandler(BaseStatusHandler):
|
||||
class IDAStatusHandler(BaseStatusHandler):
|
||||
def __init__(self):
|
||||
self.step = "Initializing"
|
||||
self.max_items = 0
|
||||
self.current_items = 0
|
||||
self.start_time = datetime.datetime.now()
|
||||
self.start_time = datetime.now()
|
||||
self.step_start_time = self.start_time
|
||||
self.last_updated_time = datetime.datetime.min
|
||||
self.last_updated_time = datetime.min
|
||||
|
||||
def initialize(self):
|
||||
ida_kernwin.show_wait_box("Processing")
|
||||
@@ -231,7 +232,7 @@ class StatusHandler(BaseStatusHandler):
|
||||
if self.was_cancelled():
|
||||
raise RuntimeError("Cancelled script.")
|
||||
|
||||
current_time = datetime.datetime.now()
|
||||
current_time = datetime.now()
|
||||
if 0.5 > (current_time - self.last_updated_time).total_seconds():
|
||||
return
|
||||
|
||||
@@ -254,8 +255,8 @@ Elapsed: {step_time} ({total_time})
|
||||
self.step = step
|
||||
self.max_items = max_items
|
||||
self.current_items = 0
|
||||
self.step_start_time = datetime.datetime.now()
|
||||
self.last_updated_time = datetime.datetime.min
|
||||
self.step_start_time = datetime.now()
|
||||
self.last_updated_time = datetime.min
|
||||
self.update()
|
||||
|
||||
def update_progress(self, new_progress = 1):
|
||||
@@ -265,5 +266,10 @@ Elapsed: {step_time} ({total_time})
|
||||
def was_cancelled(self):
|
||||
return ida_kernwin.user_cancelled()
|
||||
|
||||
def close(self):
|
||||
ida_kernwin.hide_wait_box()
|
||||
def shutdown(self):
|
||||
ida_kernwin.hide_wait_box()
|
||||
|
||||
status = IDAStatusHandler()
|
||||
backend = IDADisassemblerInterface(status)
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
@@ -1,182 +0,0 @@
|
||||
# Shared interface
|
||||
def from_hex(addr): return int(addr, 0)
|
||||
|
||||
def parse_address(d): return from_hex(d['virtualAddress'])
|
||||
|
||||
def define_il_method(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_function_type(addr, jsonDef['signature'])
|
||||
set_header_comment(addr, jsonDef['dotNetSignature'])
|
||||
add_function_to_group(addr, jsonDef['group'])
|
||||
|
||||
def define_il_method_info(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_comment(addr, jsonDef['dotNetSignature'])
|
||||
set_type(addr, r'struct MethodInfo *')
|
||||
if 'methodAddress' in jsonDef:
|
||||
add_xref(from_hex(jsonDef["methodAddress"]), addr)
|
||||
|
||||
|
||||
def define_cpp_function(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_function_type(addr, jsonDef['signature'])
|
||||
|
||||
def define_string(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_comment(addr, jsonDef['string'])
|
||||
|
||||
def define_field(addr, name, type, ilType = None):
|
||||
addr = from_hex(addr)
|
||||
set_name(addr, name)
|
||||
set_type(addr, type)
|
||||
if ilType is not None:
|
||||
set_comment(addr, ilType)
|
||||
|
||||
def define_field_from_json(jsonDef):
|
||||
define_field(jsonDef['virtualAddress'], jsonDef['name'], jsonDef['type'], jsonDef['dotNetType'])
|
||||
|
||||
def define_array(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
make_array(addr, int(jsonDef['count']), jsonDef['type'])
|
||||
set_name(addr, jsonDef['name'])
|
||||
|
||||
def define_field_with_value(jsonDef):
|
||||
addr = parse_address(jsonDef)
|
||||
set_name(addr, jsonDef['name'])
|
||||
set_comment(addr, jsonDef['value'])
|
||||
|
||||
# Process JSON
|
||||
def process_json(jsonData, status):
|
||||
# Function boundaries
|
||||
functionAddresses = jsonData['functionAddresses']
|
||||
functionAddresses.sort()
|
||||
count = len(functionAddresses)
|
||||
|
||||
status.update_step('Processing function boundaries', count)
|
||||
for i in range(count):
|
||||
start = from_hex(functionAddresses[i])
|
||||
if start == 0:
|
||||
status.update_progress()
|
||||
continue
|
||||
|
||||
end = from_hex(functionAddresses[i + 1]) if i + 1 != count else None
|
||||
|
||||
make_function(start, end)
|
||||
status.update_progress()
|
||||
|
||||
# Method definitions
|
||||
status.update_step('Processing method definitions', len(jsonData['methodDefinitions']))
|
||||
for d in jsonData['methodDefinitions']:
|
||||
define_il_method(d)
|
||||
status.update_progress()
|
||||
|
||||
# Constructed generic methods
|
||||
status.update_step('Processing constructed generic methods', len(jsonData['constructedGenericMethods']))
|
||||
for d in jsonData['constructedGenericMethods']:
|
||||
define_il_method(d)
|
||||
status.update_progress()
|
||||
|
||||
# Custom attributes generators
|
||||
status.update_step('Processing custom attributes generators', len(jsonData['customAttributesGenerators']))
|
||||
for d in jsonData['customAttributesGenerators']:
|
||||
define_cpp_function(d)
|
||||
status.update_progress()
|
||||
|
||||
# Method.Invoke thunks
|
||||
status.update_step('Processing Method.Invoke thunks', len(jsonData['methodInvokers']))
|
||||
for d in jsonData['methodInvokers']:
|
||||
define_cpp_function(d)
|
||||
status.update_progress()
|
||||
|
||||
# String literals for version >= 19
|
||||
if 'virtualAddress' in jsonData['stringLiterals'][0]:
|
||||
status.update_step('Processing string literals (V19+)', len(jsonData['stringLiterals']))
|
||||
|
||||
process_string_literals(status, jsonData)
|
||||
|
||||
# String literals for version < 19
|
||||
else:
|
||||
status.update_step('Processing string literals (pre-V19)')
|
||||
litDecl = 'enum StringLiteralIndex {\n'
|
||||
for d in jsonData['stringLiterals']:
|
||||
litDecl += " " + d['name'] + ",\n"
|
||||
litDecl += '};\n'
|
||||
define_code(litDecl)
|
||||
|
||||
# Il2CppClass (TypeInfo) pointers
|
||||
status.update_step('Processing Il2CppClass (TypeInfo) pointers', len(jsonData['typeInfoPointers']))
|
||||
for d in jsonData['typeInfoPointers']:
|
||||
define_field_from_json(d)
|
||||
status.update_progress()
|
||||
|
||||
# Il2CppType (TypeRef) pointers
|
||||
status.update_step('Processing Il2CppType (TypeRef) pointers', len(jsonData['typeRefPointers']))
|
||||
for d in jsonData['typeRefPointers']:
|
||||
define_field(d['virtualAddress'], d['name'], r'struct Il2CppType *', d['dotNetType'])
|
||||
status.update_progress()
|
||||
|
||||
# MethodInfo pointers
|
||||
status.update_step('Processing MethodInfo pointers', len(jsonData['methodInfoPointers']))
|
||||
for d in jsonData['methodInfoPointers']:
|
||||
define_il_method_info(d)
|
||||
status.update_progress()
|
||||
|
||||
# FieldInfo pointers, add the contents as a comment
|
||||
status.update_step('Processing FieldInfo pointers', len(jsonData['fields']))
|
||||
for d in jsonData['fields']:
|
||||
define_field_with_value(d)
|
||||
status.update_progress()
|
||||
|
||||
# FieldRva pointers, add the contents as a comment
|
||||
status.update_step('Processing FieldRva pointers', len(jsonData['fieldRvas']))
|
||||
for d in jsonData['fieldRvas']:
|
||||
define_field_with_value(d)
|
||||
status.update_progress()
|
||||
|
||||
# IL2CPP type metadata
|
||||
status.update_step('Processing IL2CPP type metadata', len(jsonData['typeMetadata']))
|
||||
for d in jsonData['typeMetadata']:
|
||||
define_field(d['virtualAddress'], d['name'], d['type'])
|
||||
|
||||
# IL2CPP function metadata
|
||||
status.update_step('Processing IL2CPP function metadata', len(jsonData['functionMetadata']))
|
||||
for d in jsonData['functionMetadata']:
|
||||
define_cpp_function(d)
|
||||
|
||||
# IL2CPP array metadata
|
||||
status.update_step('Processing IL2CPP array metadata', len(jsonData['arrayMetadata']))
|
||||
for d in jsonData['arrayMetadata']:
|
||||
define_array(d)
|
||||
|
||||
# IL2CPP API functions
|
||||
status.update_step('Processing IL2CPP API functions', len(jsonData['apis']))
|
||||
for d in jsonData['apis']:
|
||||
define_cpp_function(d)
|
||||
|
||||
# Entry point
|
||||
print('Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)')
|
||||
status = StatusHandler()
|
||||
status.initialize()
|
||||
|
||||
try:
|
||||
start_time = datetime.datetime.now()
|
||||
|
||||
status.update_step("Running script prologue")
|
||||
script_prologue(status)
|
||||
|
||||
with open(os.path.join(get_script_directory(), "%JSON_METADATA_RELATIVE_PATH%"), "r") as jsonFile:
|
||||
status.update_step("Loading JSON metadata")
|
||||
jsonData = json.load(jsonFile)['addressMap']
|
||||
process_json(jsonData, status)
|
||||
|
||||
status.update_step("Running script epilogue")
|
||||
script_epilogue(status)
|
||||
|
||||
status.update_step('Script execution complete.')
|
||||
print("Took: %s" % (datetime.datetime.now() - start_time))
|
||||
except RuntimeError: pass
|
||||
finally: status.close()
|
||||
@@ -1,14 +0,0 @@
|
||||
# Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)
|
||||
# Target Unity version: %TARGET_UNITY_VERSION%
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
class BaseStatusHandler:
|
||||
def initialize(self): pass
|
||||
def update_step(self, name, max_items = 0): print(name)
|
||||
def update_progress(self, progress = 1): pass
|
||||
def was_cancelled(self): return False
|
||||
def close(self): pass
|
||||
290
Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py
Normal file
290
Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)
|
||||
# Target Unity version: %TARGET_UNITY_VERSION%
|
||||
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
import abc
|
||||
|
||||
class BaseStatusHandler(abc.ABC):
|
||||
def initialize(self): pass
|
||||
def shutdown(self): pass
|
||||
|
||||
def update_step(self, name: str, max_items: int = 0): print(name)
|
||||
def update_progress(self, progress: int = 1): pass
|
||||
|
||||
def was_cancelled(self): return False
|
||||
|
||||
class BaseDisassemblerInterface(abc.ABC):
|
||||
supports_fake_string_segment: bool = False
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_script_directory(self) -> str: return ""
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_start(self): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def on_finish(self): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def define_function(self, address: int, end: int | None = None): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def define_data_array(self, address: int, type: str, count: int): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_data_type(self, address: int, type: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_function_type(self, address: int, type: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_data_comment(self, address: int, cmt: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_function_comment(self, address: int, cmt: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_data_name(self, address: int, name: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set_function_name(self, address: int, name: str): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_cross_reference(self, from_address: int, to_address: int): pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def import_c_typedef(self, type_def: str): pass
|
||||
|
||||
# optional
|
||||
def add_function_to_group(self, address: int, group: str): pass
|
||||
def cache_function_types(self, function_types: list[str]): pass
|
||||
|
||||
# only required if supports_fake_string_segment == True
|
||||
def create_fake_segment(self, name: str, size: int) -> int: return 0
|
||||
|
||||
def write_string(self, address: int, value: str): pass
|
||||
def write_address(self, address: int, value: int): pass
|
||||
|
||||
class ScriptContext:
|
||||
_backend: BaseDisassemblerInterface
|
||||
_status: BaseStatusHandler
|
||||
|
||||
def __init__(self, backend: BaseDisassemblerInterface, status: BaseStatusHandler) -> None:
|
||||
self._backend = backend
|
||||
self._status = status
|
||||
|
||||
def from_hex(self, addr: str):
|
||||
return int(addr, 0)
|
||||
|
||||
def parse_address(self, d: dict):
|
||||
return self.from_hex(d['virtualAddress'])
|
||||
|
||||
def define_il_method(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_function_name(addr, definition['name'])
|
||||
self._backend.set_function_type(addr, definition['signature'])
|
||||
self._backend.set_function_comment(addr, definition['dotNetSignature'])
|
||||
self._backend.add_function_to_group(addr, definition['group'])
|
||||
|
||||
def define_il_method_info(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_data_type(addr, r'struct MethodInfo *')
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
self._backend.set_data_comment(addr, definition['dotNetSignature'])
|
||||
if 'methodAddress' in definition:
|
||||
method_addr = self.from_hex(definition["methodAddress"])
|
||||
self._backend.add_cross_reference(method_addr, addr)
|
||||
|
||||
def define_cpp_function(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_function_name(addr, definition['name'])
|
||||
self._backend.set_function_type(addr, definition['signature'])
|
||||
|
||||
def define_string(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_data_type(addr, r'struct String *')
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
self._backend.set_data_comment(addr, definition['string'])
|
||||
|
||||
def define_field(self, addr: str, name: str, type: str, il_type: str | None = None):
|
||||
address = self.from_hex(addr)
|
||||
self._backend.set_data_type(address, type)
|
||||
self._backend.set_data_name(address, name)
|
||||
if il_type is not None:
|
||||
self._backend.set_data_comment(address, il_type)
|
||||
|
||||
def define_field_from_json(self, definition: dict):
|
||||
self.define_field(definition['virtualAddress'], definition['name'], definition['type'], definition['dotNetType'])
|
||||
|
||||
def define_array(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.define_data_array(addr, definition['type'], int(definition['count']))
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
|
||||
def define_field_with_value(self, definition: dict):
|
||||
addr = self.parse_address(definition)
|
||||
self._backend.set_data_name(addr, definition['name'])
|
||||
self._backend.set_data_comment(addr, definition['value'])
|
||||
|
||||
def process_metadata(self, metadata: dict):
|
||||
# Function boundaries
|
||||
function_addresses = metadata['functionAddresses']
|
||||
function_addresses.sort()
|
||||
count = len(function_addresses)
|
||||
|
||||
self._status.update_step('Processing function boundaries', count)
|
||||
for i in range(count):
|
||||
start = self.from_hex(function_addresses[i])
|
||||
if start == 0:
|
||||
self._status.update_progress()
|
||||
continue
|
||||
|
||||
end = self.from_hex(function_addresses[i + 1]) if i + 1 != count else None
|
||||
|
||||
self._backend.define_function(start, end)
|
||||
self._status.update_progress()
|
||||
|
||||
# Method definitions
|
||||
self._status.update_step('Processing method definitions', len(metadata['methodDefinitions']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['methodDefinitions']])
|
||||
for d in metadata['methodDefinitions']:
|
||||
self.define_il_method(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Constructed generic methods
|
||||
self._status.update_step('Processing constructed generic methods', len(metadata['constructedGenericMethods']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['constructedGenericMethods']])
|
||||
for d in metadata['constructedGenericMethods']:
|
||||
self.define_il_method(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Custom attributes generators
|
||||
self._status.update_step('Processing custom attributes generators', len(metadata['customAttributesGenerators']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['customAttributesGenerators']])
|
||||
for d in metadata['customAttributesGenerators']:
|
||||
self.define_cpp_function(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Method.Invoke thunks
|
||||
self._status.update_step('Processing Method.Invoke thunks', len(metadata['methodInvokers']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['methodInvokers']])
|
||||
for d in metadata['methodInvokers']:
|
||||
self.define_cpp_function(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# String literals for version >= 19
|
||||
if 'virtualAddress' in metadata['stringLiterals'][0]:
|
||||
self._status.update_step('Processing string literals (V19+)', len(metadata['stringLiterals']))
|
||||
|
||||
if self._backend.supports_fake_string_segment:
|
||||
total_string_length = 0
|
||||
for d in metadata['stringLiterals']:
|
||||
total_string_length += len(d["string"]) + 1
|
||||
|
||||
aligned_length = total_string_length + (4096 - (total_string_length % 4096))
|
||||
segment_base = self._backend.create_fake_segment(".fake_strings", aligned_length)
|
||||
|
||||
current_string_address = segment_base
|
||||
for d in metadata['stringLiterals']:
|
||||
self.define_string(d)
|
||||
|
||||
ref_addr = self.parse_address(d)
|
||||
self._backend.write_string(current_string_address, d["string"])
|
||||
self._backend.set_data_type(ref_addr, r'const char* const')
|
||||
self._backend.write_address(ref_addr, current_string_address)
|
||||
|
||||
current_string_address += len(d["string"]) + 1
|
||||
self._status.update_progress()
|
||||
else:
|
||||
for d in metadata['stringLiterals']:
|
||||
self.define_string(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# String literals for version < 19
|
||||
else:
|
||||
self._status.update_step('Processing string literals (pre-V19)')
|
||||
litDecl = 'enum StringLiteralIndex {\n'
|
||||
for d in metadata['stringLiterals']:
|
||||
litDecl += " " + d['name'] + ",\n"
|
||||
litDecl += '};\n'
|
||||
|
||||
self._backend.import_c_typedef(litDecl)
|
||||
|
||||
# Il2CppClass (TypeInfo) pointers
|
||||
self._status.update_step('Processing Il2CppClass (TypeInfo) pointers', len(metadata['typeInfoPointers']))
|
||||
for d in metadata['typeInfoPointers']:
|
||||
self.define_field_from_json(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# Il2CppType (TypeRef) pointers
|
||||
self._status.update_step('Processing Il2CppType (TypeRef) pointers', len(metadata['typeRefPointers']))
|
||||
for d in metadata['typeRefPointers']:
|
||||
self.define_field(d['virtualAddress'], d['name'], r'struct Il2CppType *', d['dotNetType'])
|
||||
self._status.update_progress()
|
||||
|
||||
# MethodInfo pointers
|
||||
self._status.update_step('Processing MethodInfo pointers', len(metadata['methodInfoPointers']))
|
||||
for d in metadata['methodInfoPointers']:
|
||||
self.define_il_method_info(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# FieldInfo pointers, add the contents as a comment
|
||||
self._status.update_step('Processing FieldInfo pointers', len(metadata['fields']))
|
||||
for d in metadata['fields']:
|
||||
self.define_field_with_value(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# FieldRva pointers, add the contents as a comment
|
||||
self._status.update_step('Processing FieldRva pointers', len(metadata['fieldRvas']))
|
||||
for d in metadata['fieldRvas']:
|
||||
self.define_field_with_value(d)
|
||||
self._status.update_progress()
|
||||
|
||||
# IL2CPP type metadata
|
||||
self._status.update_step('Processing IL2CPP type metadata', len(metadata['typeMetadata']))
|
||||
for d in metadata['typeMetadata']:
|
||||
self.define_field(d['virtualAddress'], d['name'], d['type'])
|
||||
|
||||
# IL2CPP function metadata
|
||||
self._status.update_step('Processing IL2CPP function metadata', len(metadata['functionMetadata']))
|
||||
for d in metadata['functionMetadata']:
|
||||
self.define_cpp_function(d)
|
||||
|
||||
# IL2CPP array metadata
|
||||
self._status.update_step('Processing IL2CPP array metadata', len(metadata['arrayMetadata']))
|
||||
for d in metadata['arrayMetadata']:
|
||||
self.define_array(d)
|
||||
|
||||
# IL2CPP API functions
|
||||
self._status.update_step('Processing IL2CPP API functions', len(metadata['apis']))
|
||||
self._backend.cache_function_types([x["signature"] for x in metadata['apis']])
|
||||
for d in metadata['apis']:
|
||||
self.define_cpp_function(d)
|
||||
|
||||
def process(self):
|
||||
self._status.initialize()
|
||||
|
||||
try:
|
||||
start_time = datetime.now()
|
||||
|
||||
self._status.update_step("Running script prologue")
|
||||
self._backend.on_start()
|
||||
|
||||
metadata_path = os.path.join(self._backend.get_script_directory(), "%JSON_METADATA_RELATIVE_PATH%")
|
||||
with open(metadata_path, "r") as f:
|
||||
self._status.update_step("Loading JSON metadata")
|
||||
metadata = json.load(f)['addressMap']
|
||||
self.process_metadata(metadata)
|
||||
|
||||
self._status.update_step("Running script epilogue")
|
||||
self._backend.on_finish()
|
||||
|
||||
self._status.update_step('Script execution complete.')
|
||||
|
||||
end_time = datetime.now()
|
||||
print(f"Took: {end_time - start_time}")
|
||||
|
||||
except RuntimeError: pass
|
||||
finally: self._status.shutdown()
|
||||
Reference in New Issue
Block a user