From a7a6a6f009bdcc655aa4fae96534f502dff5b42f Mon Sep 17 00:00:00 2001 From: SpaghettDev <37266659+SpaghettDev@users.noreply.github.com> Date: Sat, 20 Jul 2024 00:05:20 -0400 Subject: [PATCH] Fix STL types not getting set for functions that use/return them and fix exporter again --- BromaIDA.py | 2 +- broma_ida/broma/argtype.py | 60 ++++---- broma_ida/broma/binding.py | 2 +- broma_ida/broma/exporter.py | 2 +- broma_ida/broma/importer.py | 177 ++++++++++++++++++----- broma_ida/class_builder/class_builder.py | 2 +- broma_ida/types/cocos2d.hpp | 8 +- broma_ida/utils.py | 6 +- 8 files changed, 187 insertions(+), 72 deletions(-) diff --git a/BromaIDA.py b/BromaIDA.py index f7073c8..e3c88e1 100644 --- a/BromaIDA.py +++ b/BromaIDA.py @@ -1,4 +1,4 @@ -VERSION = "5.1.0" +VERSION = "5.2.0" __AUTHOR__ = "SpaghettDev" PLUGIN_NAME = "BromaIDA" diff --git a/broma_ida/broma/argtype.py b/broma_ida/broma/argtype.py index 2397cc7..cfade4a 100644 --- a/broma_ida/broma/argtype.py +++ b/broma_ida/broma/argtype.py @@ -37,30 +37,32 @@ class ArgType: """An argument type""" btype: BaseArgType - def _expand_stl_type(self, t: str) -> str: + def _expand_stl_type(self, stl_type: str) -> str: """Expands STL types because IDA is retarded and likes to expand them Args: - t (str): _description_ + stl_type (str) Returns: - str: _description_ + str """ # IDA likes spaces after types - format_pointer = lambda pt: sub(r"([^ ])\*", r"\1 *", pt) # noqa: E731 + format_pointer = lambda pt: sub( + r"([^ ])\*", r"\1 *", pt + ) # noqa: E731 - if "std::" not in t: - return t + if "std::" not in stl_type: + return stl_type if sub( - "(?: )?const(?: )?", "", t - ).removesuffix("&").removesuffix("*") == "std::string": - return t + "(?: )?const(?: )?", "", stl_type + ).removesuffix("&").removesuffix("*") == "std::string": + return stl_type - if t.startswith("std::vector"): - split_type = match(r"std::vector<(.*)>", t) + if stl_type.startswith("std::vector"): + split_type = match(r"std::vector<(.*)>", stl_type) - assert split_type is not None, "impossible" + assert split_type is not None, f"Couldn't get contained type for '{stl_type}'" if split_type.group(1) in EXPANDED_STL_TYPES: return format_pointer(f"""std::vector{ @@ -72,12 +74,12 @@ def _expand_stl_type(self, t: str) -> str: # this should never happen, but it causes the below code # to go ham - if "std::allocator" in t: - return t + if "std::allocator" in stl_type: + return stl_type # "it just works" # credit to henrysck075 i couldnt figure out this shit :D - ret = t + ret = stl_type vec_to_contained = [[ret, ret]] while True: try: @@ -110,8 +112,11 @@ def _expand_stl_type(self, t: str) -> str: return format_pointer(vec_to_contained[0][1]) - if t.startswith("std::map"): - split_type = split(r"std::map<(.*), (.*)>", t) + if stl_type.startswith("std::map"): + split_type = split(r"std::map<(.*),(?: )?(.*)>", stl_type) + + assert split_type is not None, f"Couldn't get contained types for '{stl_type}'" + map_key_type = "" map_value_type = "" @@ -164,8 +169,11 @@ def _expand_stl_type(self, t: str) -> str: "{1}", map_value_type )) - if t.startswith("std::unordered_map"): - split_type = split(r"std::unordered_map<(.*), (.*)>", t) + if stl_type.startswith("std::unordered_map"): + split_type = split(r"std::unordered_map<(.*),(?: )?(.*)>", stl_type) + + assert split_type is not None, f"Couldn't get contained types for {stl_type}" + map_key_type = "" map_value_type = "" @@ -217,10 +225,10 @@ def _expand_stl_type(self, t: str) -> str: "{1}", map_value_type )) - if t.startswith("std::set"): - contained = match("std::set<(.*)>", t) + if stl_type.startswith("std::set"): + contained = match("std::set<(.*)>", stl_type) - assert contained is not None, "impossible" + assert contained is not None, f"Couldn't get contained type for {stl_type}" return format_pointer(f"""std::set{ BASE_EXPANDED_STL_TYPES["std::set"] @@ -228,10 +236,10 @@ def _expand_stl_type(self, t: str) -> str: "{}", contained.group(1) )) - if t.startswith("std::unordered_set"): - contained = match("std::unordered_set<(.*)>", t) + if stl_type.startswith("std::unordered_set"): + contained = match("std::unordered_set<(.*)>", stl_type) - assert contained is not None, "impossible" + assert contained is not None, f"Couldn't get contained type for {stl_type}" return format_pointer(f"""std::unordered_set{ BASE_EXPANDED_STL_TYPES["std::unordered_set"] @@ -239,7 +247,7 @@ def _expand_stl_type(self, t: str) -> str: "{}", contained.group(1) )) - raise BaseException(f"[!] Couldn't expand STL type: '{t}'") + raise BaseException(f"[!] Couldn't expand STL type: '{stl_type}'") def __init__(self, btype: Union[BaseArgType, BaseShortArgType]): if btype.get("reg"): diff --git a/broma_ida/broma/binding.py b/broma_ida/broma/binding.py index 8ec7f8c..32f3afe 100644 --- a/broma_ida/broma/binding.py +++ b/broma_ida/broma/binding.py @@ -57,7 +57,7 @@ def _get_ida_qualified_name( The binding Returns: - str: _description_ + str """ if not is_visible_cp(ord("~")) and \ binding["class_name"] == binding["name"][1:]: diff --git a/broma_ida/broma/exporter.py b/broma_ida/broma/exporter.py index 77da530..4dd2437 100644 --- a/broma_ida/broma/exporter.py +++ b/broma_ida/broma/exporter.py @@ -103,7 +103,7 @@ def _get_broma_string( broma_line_no_address.string[ broma_line_no_address.span(0)[0]: ][2:] - }""" + };""" broma_binding_platforms = self._parse_method_platforms( parsed_broma_line.group(5) diff --git a/broma_ida/broma/importer.py b/broma_ida/broma/importer.py index 477c9c9..8d71ddf 100644 --- a/broma_ida/broma/importer.py +++ b/broma_ida/broma/importer.py @@ -1,7 +1,11 @@ from io import TextIOWrapper from typing import cast, Optional +from copy import deepcopy -from idaapi import get_imagebase, GN_SHORT, GN_DEMANGLED +from idaapi import ( + get_imagebase, apply_tinfo, + GN_SHORT, GN_DEMANGLED, TINFO_DEFINITE, BTF_TYPEDEF +) from idc import ( get_name as get_ea_name, get_func_flags, get_func_cmt, set_func_cmt, SetType, @@ -148,20 +152,10 @@ def get_holy_shit_struct_size(platform: BROMA_PLATFORMS) -> int: return plat_to_hss_size[platform] + # Signature stuff -class BromaImporter: - """Broma importer of all time using PyBroma now!""" - _target_platform: BROMA_PLATFORMS - _file_path: str - _has_types: bool = False - - bindings: list[Binding] = [] - # { 0xaddr: [Binding, Binding, ...] } - duplicates: dict[int, list[Binding]] = {} - - # Signature specific functions below - def _has_mismatch( - self, + @staticmethod + def has_mismatch( function: ida_func_type_data_t, binding: Binding ) -> bool: @@ -185,15 +179,14 @@ def _has_mismatch( if i == 0 and not binding["is_static"]: if str(arg) != f"""{binding["class_name"]} *""": return True - elif str(arg).replace(" *", "*") != binding["parameters"][i - 1]["type"]: + elif str(arg).replace(" *", "*") != binding["parameters"][ + i - (0 if binding["is_static"] else 1) + ]["type"]: return True return False - def _get_ida_args_str( - self, - binding: Binding - ) -> str: + def get_ida_args_str(binding: Binding) -> str: """Gets a function's argument string. Args: @@ -213,10 +206,7 @@ def _get_ida_args_str( str(arg) for arg in binding["parameters"] ]) - def _get_function_signature( - self, - binding: Binding - ) -> str: + def get_function_signature(binding: Binding) -> str: """Returns a C++ function signature for the given function. Args: @@ -230,8 +220,113 @@ def _get_function_signature( f"""{"virtual " if binding["is_virtual"] else ""}""" \ f"""{binding["return_type"]["type"]} """ \ f"""{binding["ida_qualified_name"]}""" \ - f"""({self._get_ida_args_str(binding)});""" - # End of signature specific functions + f"""({BIUtils.get_ida_args_str(binding)});""" + + @staticmethod + def set_function_signature(ea: int, binding: Binding): + """Set's the function at `ea`'s signature. Has custom logic for + functions that use STL types since those break when using SetType + because of the commas in the template arguments + + Args: + ea (int) + binding (Binding) + """ + # strip const, & and * from the type + strip_crp = lambda t: sub( + "(?: )?const(?: )?", "", t + ).removesuffix("&").removesuffix("*") + + args_has_stl_type = False + ret_has_stl_type = False + + # we don't have an issue with std::string, only with generic stl types + if "std::" in binding["return_type"]["type"] and \ + strip_crp(binding["return_type"]["type"]) != "std::string": + ret_has_stl_type = True + + for parameter in binding["parameters"]: + if strip_crp(parameter["type"]) == "std::string": + continue + + if "std::" in parameter["type"]: + args_has_stl_type = True + break + + if not args_has_stl_type and not ret_has_stl_type: + SetType(ea, BIUtils.get_function_signature(binding)) + return + + binding_fix = deepcopy(binding) + arg_stl_idx: list[int] = [] + + if args_has_stl_type: + for i in range(len(binding_fix["parameters"])): + if "std::" in binding_fix["parameters"][i]["type"]: + arg_stl_idx.append(i) + binding_fix["parameters"][i]["type"] = "void*" + + if ret_has_stl_type: + binding_fix["return_type"]["type"] = "void*" + + # first set correct amount of arguments + SetType(ea, BIUtils.get_function_signature(binding_fix)) + + function_data = BIUtils.get_function_info(ea) + + for idx in arg_stl_idx: + stl_type = ida_tinfo_t() + stl_type.get_named_type( + get_idati(), + strip_crp(binding["parameters"][idx]["type"]), + BTF_TYPEDEF, + False + ) + + if binding["parameters"][idx]["type"].endswith("&") or binding["parameters"][idx]["type"].endswith("*"): + stl_type_ptr = ida_tinfo_t() + stl_type_ptr.create_ptr(stl_type) + + stl_type = stl_type_ptr + + if "const" in binding["parameters"][idx]["type"]: + stl_type.set_const() + + function_data[idx + (0 if binding["is_static"] else 1)].type = stl_type + + if ret_has_stl_type: + stl_type = ida_tinfo_t() + stl_type.get_named_type( + get_idati(), + strip_crp(binding["return_type"]["type"]), + BTF_TYPEDEF, + False + ) + + if binding["return_type"]["type"].endswith("&") or binding["return_type"]["type"].endswith("*"): + stl_type_ptr = ida_tinfo_t() + stl_type_ptr.create_ptr(stl_type) + + stl_type = stl_type_ptr + + function_data.rettype = stl_type + + func_tinfo = ida_tinfo_t() + func_tinfo.create_func(function_data) + + # then apply the actual correct type + apply_tinfo(ea, func_tinfo, TINFO_DEFINITE) + + +class BromaImporter: + """Broma importer of all time using PyBroma now!""" + _target_platform: BROMA_PLATFORMS + _file_path: str + _has_types: bool = False + + bindings: list[Binding] = [] + # { 0xaddr: [Binding, Binding, ...] } + duplicates: dict[int, list[Binding]] = {} def _codegen_classes(self, classes: dict[str, Class]) -> bool: """Codegens the file that contains the parsed broma classes @@ -323,7 +418,8 @@ def parse_file_stream(self, file: TextIOWrapper): holy_shit_struct = BIUtils.get_type_info("holy_shit") if holy_shit_struct: - self._has_types = holy_shit_struct.get_size() == BIUtils.get_holy_shit_strut_size(self._target_platform) + self._has_types = holy_shit_struct.get_size() == \ + BIUtils.get_holy_shit_struct_size(self._target_platform) if not self._has_types: popup( @@ -369,7 +465,10 @@ def parse_file_stream(self, file: TextIOWrapper): "address": -0x1, "return_type": RetType({ "name": "", - "type": function.ret.name + "type": function.ret.name.replace( + "gd::", + "std::" + ) }), "parameters": BIUtils.from_pybroma_args( function.args @@ -436,7 +535,10 @@ def parse_file_stream(self, file: TextIOWrapper): "address": function_address, "return_type": RetType({ "name": "", - "type": function.ret.name + "type": function.ret.name.replace( + "gd::", + "std::" + ) }), "parameters": BIUtils.from_pybroma_args( function.args @@ -452,7 +554,10 @@ def parse_file_stream(self, file: TextIOWrapper): "address": function_address, "return_type": RetType({ "name": "", - "type": function.ret.name + "type": function.ret.name.replace( + "gd::", + "std::" + ) }), "parameters": BIUtils.from_pybroma_args( function.args @@ -491,21 +596,21 @@ def import_into_idb(self): if ida_ea == -0x1: continue - if self._has_mismatch( + if BIUtils.has_mismatch( cast( ida_func_type_data_t, BIUtils.get_function_info(ida_ea) ), binding ): - SetType(ida_ea, self._get_function_signature(binding)) + BIUtils.set_function_signature(ida_ea, binding) return # first, handle non-duplicates for binding in self.bindings: - ida_ea = get_imagebase() + binding["address"] - ida_name = get_ea_name(ida_ea) - ida_func_flags = get_func_flags(ida_ea) + ida_ea: int = get_imagebase() + binding["address"] + ida_name: str = get_ea_name(ida_ea) + ida_func_flags: int = get_func_flags(ida_ea) if ida_name.startswith("loc_"): add_func(ida_ea) @@ -525,12 +630,12 @@ def import_into_idb(self): continue if self._has_types and \ - self._has_mismatch(cast( + BIUtils.has_mismatch(cast( ida_func_type_data_t, BIUtils.get_function_info(ida_ea) ), binding ): - SetType(ida_ea, self._get_function_signature(binding)) + BIUtils.set_function_signature(ida_ea, binding) if ida_name.startswith("sub_"): rename_func( diff --git a/broma_ida/class_builder/class_builder.py b/broma_ida/class_builder/class_builder.py index 85e925e..f56557b 100644 --- a/broma_ida/class_builder/class_builder.py +++ b/broma_ida/class_builder/class_builder.py @@ -10,7 +10,7 @@ class ClassBuilder: _target_platform: BROMA_PLATFORMS def _import_class(self): - """_summary_""" + """Converts a Binding to a class string""" self._class_str = f"class {self._broma_class.name}" if len(self._broma_class.superclasses) != 0: diff --git a/broma_ida/types/cocos2d.hpp b/broma_ida/types/cocos2d.hpp index 913dc07..c52323f 100644 --- a/broma_ida/types/cocos2d.hpp +++ b/broma_ida/types/cocos2d.hpp @@ -530,10 +530,10 @@ namespace cocos2d // enums enum class CCObjectType { - PlayLayer = 5, - LevelEditorLayer = 6, - GameObject = 13, - MenuLayer = 15, + PlayLayerObjectType = 5, + LevelEditorLayerObjectType = 6, + GameObjectObjectType = 13, + MenuLayerObjectType = 15, }; typedef enum { diff --git a/broma_ida/utils.py b/broma_ida/utils.py index b68688b..b25c6d8 100644 --- a/broma_ida/utils.py +++ b/broma_ida/utils.py @@ -176,10 +176,12 @@ def get_platform_printable(platform: BROMA_PLATFORMS) -> str: def get_ida_plugin_path() -> Optional[Path]: - """_summary_ + """Gets the plugin path of the IDA folder using magic + (why isnt this exported by the idapython api) Returns: - str: _description_ + Optional[Path]: The plugin path as a Path, + or None if it couldn't be found """ paths = [path for path in sys_path if "plugins" in path]