From cfccc6982c0c499b083f9128c5ec5977ba223076 Mon Sep 17 00:00:00 2001 From: LukeFZ <17146677+LukeFZ@users.noreply.github.com> Date: Fri, 26 Dec 2025 05:48:20 +0100 Subject: [PATCH] reformat disassembler scripts --- .../ScriptResources/Targets/BinaryNinja.py | 28 +- .../Outputs/ScriptResources/Targets/Ghidra.py | 210 ++++--- .../Outputs/ScriptResources/Targets/IDA.py | 415 +++++++------ .../Outputs/ScriptResources/shared_base.py | 544 ++++++++++-------- 4 files changed, 677 insertions(+), 520 deletions(-) diff --git a/Il2CppInspector.Common/Outputs/ScriptResources/Targets/BinaryNinja.py b/Il2CppInspector.Common/Outputs/ScriptResources/Targets/BinaryNinja.py index 94553de..9b21ead 100644 --- a/Il2CppInspector.Common/Outputs/ScriptResources/Targets/BinaryNinja.py +++ b/Il2CppInspector.Common/Outputs/ScriptResources/Targets/BinaryNinja.py @@ -15,18 +15,22 @@ from binaryninja import ( ) from binaryninja.log import log_error -# 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 -# from typing import Literal -# bv: BinaryView = None # type: ignore -# except: -# pass +try: + from typing import TYPE_CHECKING + + if TYPE_CHECKING: + from ..shared_base import ( + BaseStatusHandler, + BaseDisassemblerInterface, + ScriptContext, + ) + import os + from datetime import datetime + from typing import Literal, Union + + bv: BinaryView = None # type: ignore +except ImportError: + pass CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) diff --git a/Il2CppInspector.Common/Outputs/ScriptResources/Targets/Ghidra.py b/Il2CppInspector.Common/Outputs/ScriptResources/Targets/Ghidra.py index 930d251..8017b7c 100644 --- a/Il2CppInspector.Common/Outputs/ScriptResources/Targets/Ghidra.py +++ b/Il2CppInspector.Common/Outputs/ScriptResources/Targets/Ghidra.py @@ -8,121 +8,145 @@ from ghidra.app.cmd.label import DemanglerCmd from ghidra.app.services import DataTypeManagerService from java.lang import Long -#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 +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 ImportError: + pass + class GhidraDisassemblerInterface(BaseDisassemblerInterface): - supports_fake_string_segment = False + supports_fake_string_segment = False - def _to_address(self, value): - return toAddr(Long(value)) + def _to_address(self, value): + return toAddr(Long(value)) - def get_script_directory(self) -> str: - return getSourceFile().getParentFile().toString() + def get_script_directory(self) -> str: + return getSourceFile().getParentFile().toString() - def on_start(self): - self.xrefs = currentProgram.getReferenceManager() + def on_start(self): + self.xrefs = currentProgram.getReferenceManager() - # 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() + # 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(self._to_address(0), True) - - # Don't trigger decompiler - setAnalysisOption(currentProgram, "Call Convention ID", "false") + # 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(self._to_address(0), True) - def on_finish(self): - pass + # Don't trigger decompiler + setAnalysisOption(currentProgram, "Call Convention ID", "false") - def define_function(self, address: int, end: Union[int, None] = None): - address = self._to_address(address) - # Don't override existing functions - fn = getFunctionAt(address) - if fn is None: - # Create new function if none exists - createFunction(address, None) + def on_finish(self): + pass - 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 = self._to_address(address) - removeDataAt(address) - createData(address, a) + def define_function(self, address: int, end: Union[int, None] = None): + address = self._to_address(address) + # Don't override existing functions + fn = getFunctionAt(address) + if fn is None: + # Create new function if none exists + createFunction(address, None) - def set_data_type(self, address: int, type: str): - if type.startswith('struct '): - type = type[7:] - - try: - t = getDataTypes(type)[0] - address = self._to_address(address) - removeDataAt(address) - createData(address, t) - except: - print("Failed to set type: %s" % type) + def define_data_array(self, address: int, type: str, count: int): + if type.startswith("struct "): + type = type[7:] - def set_function_type(self, address: int, type: str): - typeSig = CParserUtils.parseSignature(DataTypeManagerService@None, currentProgram, type) - ApplyFunctionSignatureCmd(self._to_address(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram) + t = getDataTypes(type)[0] + a = ArrayDataType(t, count, t.getLength()) + address = self._to_address(address) + removeDataAt(address) + createData(address, a) - def set_data_comment(self, address: int, cmt: str): - setEOLComment(self._to_address(address), cmt) + def set_data_type(self, address: int, type: str): + if type.startswith("struct "): + type = type[7:] - def set_function_comment(self, address: int, cmt: str): - setPlateComment(self._to_address(address), cmt) + try: + t = getDataTypes(type)[0] + address = self._to_address(address) + removeDataAt(address) + createData(address, t) + except: + print("Failed to set type: %s" % type) - def set_data_name(self, address: int, name: str): - address = self._to_address(address) + def set_function_type(self, address: int, type: str): + typeSig = CParserUtils.parseSignature( + DataTypeManagerService @ None, currentProgram, type + ) + ApplyFunctionSignatureCmd( + self._to_address(address), typeSig, SourceType.USER_DEFINED, False, True + ).applyTo(currentProgram) - if len(name) > 2000: - print("Name length exceeds 2000 characters, skipping (%s)" % name) - return + def set_data_comment(self, address: int, cmt: str): + setEOLComment(self._to_address(address), cmt) - 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_comment(self, address: int, cmt: str): + setPlateComment(self._to_address(address), cmt) - def set_function_name(self, address: int, name: str): - return self.set_data_name(address, name) + def set_data_name(self, address: int, name: str): + address = self._to_address(address) - def add_cross_reference(self, from_address: int, to_address: int): - self.xrefs.addMemoryReference(self._to_address(from_address), self._to_address(to_address), RefType.DATA, SourceType.USER_DEFINED, 0) + if len(name) > 2000: + print("Name length exceeds 2000 characters, skipping (%s)" % name) + return - 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 + 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( + self._to_address(from_address), + self._to_address(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 -class GhidraStatusHandler(BaseStatusHandler): - pass status = GhidraStatusHandler() backend = GhidraDisassemblerInterface() context = ScriptContext(backend, status) -context.process() \ No newline at end of file +context.process() diff --git a/Il2CppInspector.Common/Outputs/ScriptResources/Targets/IDA.py b/Il2CppInspector.Common/Outputs/ScriptResources/Targets/IDA.py index 886036d..7954994 100644 --- a/Il2CppInspector.Common/Outputs/ScriptResources/Targets/IDA.py +++ b/Il2CppInspector.Common/Outputs/ScriptResources/Targets/IDA.py @@ -13,261 +13,300 @@ import ida_xref import ida_pro if ida_pro.IDA_SDK_VERSION > 770: - import ida_srclang - import ida_dirtree - IDACLANG_AVAILABLE = True - FOLDERS_AVAILABLE = True - print("IDACLANG available") + import ida_srclang + import ida_dirtree + + IDACLANG_AVAILABLE = True + FOLDERS_AVAILABLE = True + print("IDACLANG available") else: - IDACLANG_AVAILABLE = False - FOLDERS_AVAILABLE = False + IDACLANG_AVAILABLE = False + FOLDERS_AVAILABLE = False -#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 +try: + from typing import TYPE_CHECKING, Union + + if TYPE_CHECKING: + from ..shared_base import ( + BaseStatusHandler, + BaseDisassemblerInterface, + ScriptContext, + ) + import os + from datetime import datetime +except ImportError: + pass + +TINFO_DEFINITE = ( + 0x0001 # These only exist in idc for some reason, so we redefine it here +) +DEFAULT_TIL: "ida_typeinf.til_t" = None # type: ignore -TINFO_DEFINITE = 0x0001 # These only exist in idc for some reason, so we redefine it here -DEFAULT_TIL: "til_t" = None # type: ignore class IDADisassemblerInterface(BaseDisassemblerInterface): - supports_fake_string_segment = True + supports_fake_string_segment = True - _status: BaseStatusHandler + _status: BaseStatusHandler - _type_cache: dict - _folders: list - - _function_dirtree: "ida_dirtree.dirtree_t" - _cached_genflags: int - _skip_function_creation: bool - _is_32_bit: bool - _fake_segments_base: int + _type_cache: dict + _folders: list - def __init__(self, status: BaseStatusHandler): - self._status = status - - self._type_cache = {} - self._folders = [] + _function_dirtree: "ida_dirtree.dirtree_t" + _cached_genflags: int + _skip_function_creation: bool + _is_32_bit: bool + _fake_segments_base: int - self._cached_genflags = 0 - self._skip_function_creation = False - self._is_32_bit = False - self._fake_segments_base = 0 + def __init__(self, status: BaseStatusHandler): + self._status = status - 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 = {} + self._folders = [] - self._type_cache[type] = info[1:] + self._cached_genflags = 0 + self._skip_function_creation = False + self._is_32_bit = False + self._fake_segments_base = 0 - return self._type_cache[type] + 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 - def get_script_directory(self) -> str: - return os.path.dirname(os.path.realpath(__file__)) + self._type_cache[type] = info[1:] - 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) + return self._type_cache[type] - # Unload type libraries we know to cause issues - like the c++ linux one - PLATFORMS = ["x86", "x64", "arm", "arm64", "win7"] - PROBLEMATIC_TYPELIBS = ["gnulnx", "mssdk64"] + def get_script_directory(self) -> str: + return os.path.dirname(os.path.realpath(__file__)) - for lib in PROBLEMATIC_TYPELIBS: - for platform in PLATFORMS: - ida_typeinf.del_til(f"{lib}_{platform}") + 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) - # 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) + # Unload type libraries we know to cause issues - like the c++ linux one + PLATFORMS = ["x86", "x64", "arm", "arm64", "win7"] + PROBLEMATIC_TYPELIBS = ["gnulnx", "mssdk64"] - self._status.update_step('Processing Types') + for lib in PROBLEMATIC_TYPELIBS: + for platform in PLATFORMS: + ida_typeinf.del_til(f"{lib}_{platform}") - 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) + # 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) - # Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow - self._skip_function_creation = ida_segment.get_segm_by_name(".pdata") is not None - if self._skip_function_creation: - print(".pdata section found, skipping function boundaries") + self._status.update_step("Processing Types") - if FOLDERS_AVAILABLE: - self._function_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS) + 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) - self._is_32_bit = ida_ida.inf_is_32bit_exactly() + # Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow + self._skip_function_creation = ( + ida_segment.get_segm_by_name(".pdata") is not None + ) + if self._skip_function_creation: + print(".pdata section found, skipping function boundaries") - def on_finish(self): - ida_ida.inf_set_genflags(self._cached_genflags) + if FOLDERS_AVAILABLE: + self._function_dirtree = ida_dirtree.get_std_dirtree( + ida_dirtree.DIRTREE_FUNCS + ) - def define_function(self, address: int, end: Union[int, None] = None): - if self._skip_function_creation: - return + self._is_32_bit = ida_ida.inf_is_32bit_exactly() - 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 on_finish(self): + ida_ida.inf_set_genflags(self._cached_genflags) - def define_data_array(self, address: int, type: str, count: int): - self.set_data_type(address, type) + def define_function(self, address: int, end: Union[int, None] = None): + if self._skip_function_creation: + return - 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.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" + ) - ida_bytes.create_data(address, flags, count * entrySize, tid) + def define_data_array(self, address: int, type: str, count: int): + self.set_data_type(address, type) - def set_data_type(self, address: int, type: str): - 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 - info = self._get_type(type) - if info is None: - return + ida_bytes.create_data(address, flags, count * entrySize, tid) - 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_data_type(self, address: int, type: str): + type += ";" - def set_function_type(self, address: int, type: str): - self.set_data_type(address, type) + info = self._get_type(type) + if info is None: + return - def set_data_comment(self, address: int, cmt: str): - ida_bytes.set_cmt(address, cmt, False) + 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_comment(self, address: int, cmt: str): - func = ida_funcs.get_func(address) - if func is None: - return + def set_function_type(self, address: int, type: str): + self.set_data_type(address, type) - ida_funcs.set_func_cmt(func, cmt, True) + def set_data_comment(self, address: int, cmt: str): + ida_bytes.set_cmt(address, cmt, False) - 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_comment(self, address: int, cmt: str): + func = ida_funcs.get_func(address) + if func is None: + return - def set_function_name(self, address: int, name: str): - self.set_data_name(address, name) + ida_funcs.set_func_cmt(func, cmt, True) - 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 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 import_c_typedef(self, type_def: str): - ida_typeinf.idc_parse_types(type_def, 0) + def set_function_name(self, address: int, name: str): + self.set_data_name(address, name) - # optional - def add_function_to_group(self, address: int, group: str): - if not FOLDERS_AVAILABLE or ida_pro.IDA_SDK_VERSION < 930: # enable at your own risk on pre 9.3 - this is slow - return + 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) - if group not in self._folders: - self._folders.append(group) - self._function_dirtree.mkdir(group) + def import_c_typedef(self, type_def: str): + ida_typeinf.idc_parse_types(type_def, 0) - name = ida_funcs.get_func_name(address) - self._function_dirtree.rename(name, f"{group}/{name}") + # optional + def add_function_to_group(self, address: int, group: str): + if ( + not FOLDERS_AVAILABLE or ida_pro.IDA_SDK_VERSION < 930 + ): # enable at your own risk on pre 9.3 - this is slow + return - # 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 + if group not in self._folders: + self._folders.append(group) + self._function_dirtree.mkdir(group) - 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() + name = ida_funcs.get_func_name(address) + self._function_dirtree.rename(name, f"{group}/{name}") - return start + # 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 - def write_string(self, address: int, value: str) -> int: - 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) - return string_length + 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) -> int: + 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) + return string_length + + 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) - 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 IDAStatusHandler(BaseStatusHandler): - def __init__(self): - 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 - - def initialize(self): - ida_kernwin.show_wait_box("Processing") + def __init__(self): + 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 - def update(self): - if self.was_cancelled(): - raise RuntimeError("Cancelled script.") + def initialize(self): + ida_kernwin.show_wait_box("Processing") - current_time = datetime.now() - if 0.5 > (current_time - self.last_updated_time).total_seconds(): - return + def update(self): + if self.was_cancelled(): + raise RuntimeError("Cancelled script.") - self.last_updated_time = current_time + current_time = datetime.now() + if 0.5 > (current_time - self.last_updated_time).total_seconds(): + return - step_time = current_time - self.step_start_time - total_time = current_time - self.start_time - message = f""" + self.last_updated_time = current_time + + step_time = current_time - self.step_start_time + total_time = current_time - self.start_time + message = f""" Running IL2CPP script. Current Step: {self.step} Progress: {self.current_items}/{self.max_items} Elapsed: {step_time} ({total_time}) """ - ida_kernwin.replace_wait_box(message) + ida_kernwin.replace_wait_box(message) - def update_step(self, step, max_items = 0): - print(step) + def update_step(self, step, max_items=0): + print(step) - 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() + 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 update_progress(self, new_progress=1): + self.current_items += new_progress + self.update() - def was_cancelled(self): - return ida_kernwin.user_cancelled() + def was_cancelled(self): + return ida_kernwin.user_cancelled() + + def shutdown(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() \ No newline at end of file +context.process() diff --git a/Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py b/Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py index 6ad2778..e82a9c3 100644 --- a/Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py +++ b/Il2CppInspector.Common/Outputs/ScriptResources/shared_base.py @@ -1,4 +1,4 @@ -#@runtime PyGhidra +# @runtime PyGhidra # ^-- required for ghidra, ignored by all others # Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty) @@ -10,288 +10,378 @@ from datetime import datetime from typing import Union import abc + class BaseStatusHandler(abc.ABC): - def initialize(self): pass - def shutdown(self): pass + def initialize(self): + pass - def update_step(self, name: str, max_items: int = 0): print(name) - def update_progress(self, progress: int = 1): 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 - def was_cancelled(self): return False class BaseDisassemblerInterface(abc.ABC): - supports_fake_string_segment: bool = False + supports_fake_string_segment: bool = False - @abc.abstractmethod - def get_script_directory(self) -> str: return "" + @abc.abstractmethod + def get_script_directory(self) -> str: + return "" - @abc.abstractmethod - def on_start(self): pass + @abc.abstractmethod + def on_start(self): + pass - @abc.abstractmethod - def on_finish(self): pass + @abc.abstractmethod + def on_finish(self): + pass - @abc.abstractmethod - def define_function(self, address: int, end: Union[int, None] = None): pass + @abc.abstractmethod + def define_function(self, address: int, end: Union[int, None] = None): + pass - @abc.abstractmethod - def define_data_array(self, address: int, type: str, count: int): 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_data_type(self, address: int, type: str): + pass - @abc.abstractmethod - def set_function_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_data_comment(self, address: int, cmt: str): + pass - @abc.abstractmethod - def set_function_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_data_name(self, address: int, name: str): + pass - @abc.abstractmethod - def set_function_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 add_cross_reference(self, from_address: int, to_address: int): + pass - @abc.abstractmethod - def import_c_typedef(self, type_def: str): 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 + # optional + def add_function_to_group(self, address: int, group: str): + pass - # only required if supports_fake_string_segment == True - def create_fake_segment(self, name: str, size: int) -> int: return 0 + 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) -> int: + return 0 + + def write_address(self, address: int, value: int): + pass - def write_string(self, address: int, value: str) -> int: pass - def write_address(self, address: int, value: int): pass class ScriptContext: - _backend: BaseDisassemblerInterface - _status: BaseStatusHandler + _backend: BaseDisassemblerInterface + _status: BaseStatusHandler - def __init__(self, backend: BaseDisassemblerInterface, status: BaseStatusHandler) -> None: - self._backend = backend - self._status = status + 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 from_hex(self, addr: str): + return int(addr, 0) - def parse_address(self, d: dict): - return self.from_hex(d['virtualAddress']) + 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(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_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_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_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_field(self, addr: str, name: str, type: str, il_type: Union[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_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_from_json(self, definition: dict): - self.define_field(definition['virtualAddress'], definition['name'], definition['type'], definition['dotNetType']) + def define_field( + self, addr: str, name: str, type: str, il_type: Union[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_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_from_json(self, definition: dict): + self.define_field( + definition["virtualAddress"], + definition["name"], + definition["type"], + definition["dotNetType"], + ) - 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 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 process_metadata(self, metadata: dict): - # Function boundaries - function_addresses = metadata['functionAddresses'] - function_addresses.sort() - count = len(function_addresses) + 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"]) - 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 + def process_metadata(self, metadata: dict): + # Function boundaries + function_addresses = metadata["functionAddresses"] + function_addresses.sort() + count = len(function_addresses) - end = self.from_hex(function_addresses[i + 1]) if i + 1 != count else None + 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 - self._backend.define_function(start, end) - self._status.update_progress() + end = self.from_hex(function_addresses[i + 1]) if i + 1 != count else None - # 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() + self._backend.define_function(start, end) + 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() + # 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() - # String literals for version >= 19 - if 'virtualAddress' in metadata['stringLiterals'][0]: - self._status.update_step('Processing string literals (V19+)', len(metadata['stringLiterals'])) + # 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() - 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) + # 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() - current_string_address = segment_base - for d in metadata['stringLiterals']: - self.define_string(d) + # 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() - ref_addr = self.parse_address(d) - written_string_length = 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) + # String literals for version >= 19 + if "virtualAddress" in metadata["stringLiterals"][0]: + self._status.update_step( + "Processing string literals (V19+)", len(metadata["stringLiterals"]) + ) - current_string_address += written_string_length - self._status.update_progress() - else: - for d in metadata['stringLiterals']: - self.define_string(d) - self._status.update_progress() + if self._backend.supports_fake_string_segment: + total_string_length = 0 + for d in metadata["stringLiterals"]: + total_string_length += len(d["string"]) + 1 - # 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' + aligned_length = total_string_length + ( + 4096 - (total_string_length % 4096) + ) + segment_base = self._backend.create_fake_segment( + ".fake_strings", aligned_length + ) - 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() + current_string_address = segment_base + for d in metadata["stringLiterals"]: + self.define_string(d) - # 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() + ref_addr = self.parse_address(d) + written_string_length = 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) - # 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() + current_string_address += written_string_length + self._status.update_progress() + else: + for d in metadata["stringLiterals"]: + self.define_string(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) + # 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" - # IL2CPP array metadata - self._status.update_step('Processing IL2CPP array metadata', len(metadata['arrayMetadata'])) - for d in metadata['arrayMetadata']: - self.define_array(d) + self._backend.import_c_typedef(litDecl) - # 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) + # 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() - def process(self): - self._status.initialize() + # 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() - try: - start_time = datetime.now() + # 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() - self._status.update_step("Running script prologue") - self._backend.on_start() + # 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() - 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) + # 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() - self._status.update_step("Running script epilogue") - self._backend.on_finish() + # 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"]) - self._status.update_step('Script execution complete.') + # IL2CPP function metadata + self._status.update_step( + "Processing IL2CPP function metadata", len(metadata["functionMetadata"]) + ) + for d in metadata["functionMetadata"]: + self.define_cpp_function(d) - end_time = datetime.now() - print(f"Took: {end_time - start_time}") + # IL2CPP array metadata + self._status.update_step( + "Processing IL2CPP array metadata", len(metadata["arrayMetadata"]) + ) + for d in metadata["arrayMetadata"]: + self.define_array(d) - except RuntimeError: - pass - - finally: - self._status.shutdown() \ No newline at end of file + # 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()