diff --git a/ida/data-validator.js b/ida/data-validator.js index a1c34c756..02ee3b0c1 100644 --- a/ida/data-validator.js +++ b/ida/data-validator.js @@ -65,6 +65,8 @@ try { errors.push(`Invalid value for classes.${key}.vtbls.${i}.base: ${vtbl.base}`); else if (!vtbl.hasOwnProperty('base') && i !== 0) errors.push(`Missing value for classes.${key}.vtbls.${i}.base`); + if (vtbl.hasOwnProperty('size') && !isNumeric(vtbl.size)) + errors.push(`Invalid value for classes.${key}.vtbls.${i}.size: ${vtbl.size}`); }); if (classI.hasOwnProperty('vfuncs') && classI["vfuncs"]) Object.keys(classI.vfuncs).forEach(subkey => { diff --git a/ida/data.yml b/ida/data.yml index d3686ea01..488d68c6f 100644 --- a/ida/data.yml +++ b/ida/data.yml @@ -2369,6 +2369,10 @@ classes: 0x140E366A0: GetPlotIcon # (this, plotIndex) # inlined @ 140D1ECF0 0x140E33FA0: IsEstateShared # (this, plotIndex) 0x140E33E80: IsEstateResident # (this, plotIndex) + Client::Game::IndoorTerritory::OutdoorTerritoryUIEventListener: + vtbls: + - ea: 0x1421CEC88 + base: Component::GUI::AtkModuleInterface::AtkEventInterface # technically Client::Game::HousingUIEventListener Client::Game::IndoorTerritory::IndoorTerritoryUIEventListener: vtbls: - ea: 0x1421CECA0 @@ -5408,6 +5412,7 @@ classes: vtbls: - ea: 0x1421344A0 base: Client::UI::RaptureAtkModule::ItemCache + size: 4 # script detects this as 1 due to xref to vfunc 1 Client::UI::RaptureAtkModule::CrystalCache: vtbls: - ea: 0x1421344C0 @@ -5415,6 +5420,7 @@ classes: Component::GUI::AtkModuleInterface::AtkHistoryInterface: vtbls: - ea: 0x1421340B0 + size: 6 # script detects this as 8 due to following vtbl with no xref vfuncs: 0: dtor 1: Previous @@ -17819,6 +17825,7 @@ classes: vtbls: - ea: 0x1421DE420 base: Client::Game::Event::GatheringEventHandler + size: 291 # base class for an AtkModuleInterface::AtkEventInterface child for specific handler with no xrefs after this funcs: 0x140E97DE0: ctor # not used due to being inlined Client::Game::Event::SpearFishingEventHandler: @@ -17947,6 +17954,7 @@ classes: vtbls: - ea: 0x1421DD1F0 base: Client::Game::Event::EventHandler + size: 291 # base class for an AtkModuleInterface::AtkEventInterface child for specific handler with no xrefs after this funcs: 0x140E90680: ctor 0x140E8E000: Setup @@ -18262,6 +18270,7 @@ classes: vtbls: - ea: 0x142394E90 base: Client::Game::Event::LuaEventHandler + size: 278 # base class for an AtkModuleInterface::AtkEventInterface child for specific handler with no xrefs after this funcs: 0x1419F4910: GetChocoboTaxiStandId 0x1419F49B0: GetFare @@ -18532,6 +18541,7 @@ classes: vtbls: - ea: 0x142419130 base: Client::Game::Event::CustomTalkResidentEventHandler + size: 286 # base class for an AtkModuleInterface::AtkEventInterface child for specific handler with no xrefs after this Client::Game::Event::JournalCallback: vtbls: - ea: 0x1421CC3C8 diff --git a/ida/data_schema.py b/ida/data_schema.py index 6f2face66..b0ea1b96b 100644 --- a/ida/data_schema.py +++ b/ida/data_schema.py @@ -6,10 +6,11 @@ def __init__(self, ea, pointer=False, name=None): self.name = name class DefinedDataClassVtable: - def __init__(self, ea, base=None): - # type: (int, None | str) -> None + def __init__(self, ea, base=None, size=None): + # type: (int, None | str, None | int) -> None self.ea = ea self.base = base + self.size = size class DefinedDataClassFunction: def __init__(self, num, name): diff --git a/ida/ffxiv_idarename.py b/ida/ffxiv_idarename.py index 4821589b1..076ca9d0b 100644 --- a/ida/ffxiv_idarename.py +++ b/ida/ffxiv_idarename.py @@ -556,7 +556,7 @@ def load_data(): class_data = {} vtbls_raw = class_data.pop("vtbls", []) - vtbls = [(vtbl["ea"], vtbl["base"] if "base" in vtbl else None) for vtbl in vtbls_raw] + vtbls = [(vtbl["ea"], vtbl["base"] if "base" in vtbl else None, vtbl["size"] if "size" in vtbl else None) for vtbl in vtbls_raw] vfuncs = class_data.pop("vfuncs", {}) funcs = class_data.pop("funcs", {}) instances_raw = class_data.pop("instances", []) @@ -599,7 +599,7 @@ def register(self, class_name, vtbls=None, vfuncs=None, funcs=None, instances=No if not funcs: funcs = {} - for (vtbl_ea, _) in vtbls: + for (vtbl_ea, _, _) in vtbls: if vtbl_ea != 0x0 and vtbl_ea in self._vtbl_addresses: print("Error: Multiple vtables are defined at 0x{0:X}".format(vtbl_ea)) return @@ -607,7 +607,7 @@ def register(self, class_name, vtbls=None, vfuncs=None, funcs=None, instances=No if class_name in self._classes: print("Error: Multiple classes are registered with the name \"{0}\"".format(class_name)) return - for (vtbl_ea, _) in vtbls: + for (vtbl_ea, _, _) in vtbls: self._vtbl_addresses.append(vtbl_ea) if not instances: @@ -680,16 +680,19 @@ def _finalize_class(self, cls): class Vtbl: resolved_base = None # type: FfxivClass - def __init__(self, ea, base_name=None): + def __init__(self, ea, base_name=None, size=None): """ Object representing a class's vtbl :param ea: Address of vtbl :type ea: int :param base_name: Name of the base class this vtbl inherits, if it exists :type base_name: str + :param size: Explicit size of vtbl used to override cases where size detection fails + :type size: int """ self.ea = ea self.base_name = base_name + self.size = size class FfxivClass: @@ -713,7 +716,7 @@ def __init__(self, class_name, vtbls, vfuncs, funcs, instances): """ self.name = class_name if vtbls: - self.vtbls = [Vtbl(ea, base_name) for (ea, base_name) in vtbls] + self.vtbls = [Vtbl(ea, base_name, size) for (ea, base_name, size) in vtbls] self.vfuncs = vfuncs self.funcs = funcs self.instances = instances @@ -737,12 +740,17 @@ def main_vtbl_size(self): """ Iterate from the vtbl start until a non-offset or xref is encountered. This strategy implies that the only xref in a vtbl is the first vfunc. + Can be overriden by explicit size in the vtbl definition. :return: VTable func count :rtype: int """ if not self.vtbls: return self._main_vtbl_size + if self.vtbls[0].size: + self._main_vtbl_size = self.vtbls[0].size + return self._main_vtbl_size + if self._main_vtbl_size == 0: self._main_vtbl_size = 1 # Set to 1, skip the first entry for ea in itertools.count(self.vtbls[0].ea + 8, 8):