mirror of
https://github.com/zeldaret/botw
synced 2026-10-09 13:37:01 -04:00
tools: drop the AI class generators and their aidef data
The AI/Action/Query stub generators, the stub matcher and the IDA vfunc renamers are no longer useful now that: - every class has hand-edited source code - IDB naming work has superseded the placeholder names used by the scripts The scripts don't even import any more so we should just remove them. (AI assisted using Claude Opus 5.5)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,3 @@
|
||||
// DO NOT MAKE MAJOR EDITS. This file is automatically generated.
|
||||
// For major edits, please edit the generator script (ai_generate_queries.py) instead.
|
||||
// If edits are made to this file, make sure they are not lost when the generator is re-run.
|
||||
|
||||
#include "Game/Actor/aiActionFactories.h"
|
||||
#include <array>
|
||||
#include "Game/Actor/Action/actionASPlaySimpleAnmDriven.h"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// DO NOT MAKE MAJOR EDITS. This file is automatically generated.
|
||||
// For major edits, please edit the generator script (ai_generate_ais.py) instead.
|
||||
// If edits are made to this file, make sure they are not lost when the generator is re-run.
|
||||
|
||||
#include "Game/Actor/aiAiFactories.h"
|
||||
#include <array>
|
||||
#include "Game/Actor/AI/aiASWeaponRoot.h"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// DO NOT MAKE MAJOR EDITS. This file is automatically generated.
|
||||
// For major edits, please edit the generator script (ai_generate_queries.py) instead.
|
||||
// If edits are made to this file, make sure they are not lost when the generator is re-run.
|
||||
|
||||
#include "Game/Actor/aiQueryFactories.h"
|
||||
#include <array>
|
||||
#include "Game/Actor/Query/queryBranchByDyeColor.h"
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
from typing import Dict, List
|
||||
import yaml
|
||||
|
||||
from common.util import utils
|
||||
from common.util.graph import Graph
|
||||
|
||||
BaseClasses = {
|
||||
0x71024d8d68,
|
||||
0x71025129f0,
|
||||
0x7102513278,
|
||||
0x71024d8ef0,
|
||||
0x710243c9b8,
|
||||
}
|
||||
|
||||
|
||||
def check_vtable_name_dict(names: Dict[int, str]):
|
||||
seen = set()
|
||||
for k, v in names.items():
|
||||
if v in seen:
|
||||
raise ValueError(f"invalid vtable names: {v} appears twice")
|
||||
seen.add(k)
|
||||
seen.add(v)
|
||||
|
||||
|
||||
def get_vtables() -> Dict[str, Dict[str, List[int]]]:
|
||||
with (utils.get_repo_root() / "data" / "aidef_vtables.yml").open(encoding="utf-8") as f:
|
||||
return yaml.load(f, Loader=yaml.CSafeLoader)
|
||||
|
||||
|
||||
def get_action_params() -> Dict[str, List[dict]]:
|
||||
with (utils.get_repo_root() / "data" / "aidef_action_params.yml").open(encoding="utf-8") as f:
|
||||
return yaml.load(f, Loader=yaml.CSafeLoader)
|
||||
|
||||
|
||||
def get_action_vtable_names() -> Dict[int, str]:
|
||||
with (utils.get_repo_root() / "data" / "aidef_action_vtables.yml").open(encoding="utf-8") as f:
|
||||
names = yaml.load(f, Loader=yaml.CSafeLoader)
|
||||
|
||||
check_vtable_name_dict(names)
|
||||
return names
|
||||
|
||||
|
||||
def get_ai_params() -> Dict[str, List[dict]]:
|
||||
with (utils.get_repo_root() / "data" / "aidef_ai_params.yml").open(encoding="utf-8") as f:
|
||||
return yaml.load(f, Loader=yaml.CSafeLoader)
|
||||
|
||||
|
||||
def get_ai_vtable_names() -> Dict[int, str]:
|
||||
with (utils.get_repo_root() / "data" / "aidef_ai_vtables.yml").open(encoding="utf-8") as f:
|
||||
names = yaml.load(f, Loader=yaml.CSafeLoader)
|
||||
|
||||
check_vtable_name_dict(names)
|
||||
return names
|
||||
|
||||
|
||||
def topologically_sort_vtables(all_vtables: dict, type_: str) -> List[int]:
|
||||
graph = Graph()
|
||||
for name, vtables in all_vtables[type_].items():
|
||||
classes = list(dict.fromkeys(reversed(vtables)))
|
||||
for i in range(len(classes) - 1):
|
||||
graph.add_edge(classes[i + 1], classes[i])
|
||||
return graph.topological_sort()
|
||||
|
||||
|
||||
def find_header(class_dir, name: str) -> str:
|
||||
"""Include path for an AI class header, wherever it lives under src/ (feature folders included)."""
|
||||
src = class_dir
|
||||
while src.name != "src":
|
||||
src = src.parent
|
||||
default = (class_dir / name).relative_to(src).as_posix()
|
||||
if (class_dir / name).exists():
|
||||
return default
|
||||
hits = sorted(src.rglob(name))
|
||||
return hits[0].relative_to(src).as_posix() if hits else default
|
||||
@@ -1,272 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import enum
|
||||
|
||||
import cxxfilt
|
||||
import zlib
|
||||
from typing import List, Dict, Iterable, Optional, Set
|
||||
|
||||
from pathlib import Path
|
||||
import textwrap
|
||||
import ai_common
|
||||
from common.util import elf
|
||||
|
||||
|
||||
def get_member_name(entry) -> str:
|
||||
type_ = entry["type"]
|
||||
if type_ == "dynamic_param":
|
||||
return f'm{entry["param_name"]}_d'
|
||||
elif type_ == "dynamic2_param":
|
||||
return f'm{entry["param_name"]}_d'
|
||||
elif type_ == "static_param":
|
||||
return f'm{entry["param_name"]}_s'
|
||||
elif type_ == "map_unit_param":
|
||||
return f'm{entry["param_name"]}_m'
|
||||
elif type_ == "aitree_variable":
|
||||
return f'm{entry["param_name"]}_a'
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def generate_action_loadparam_body(info: list) -> str:
|
||||
out = []
|
||||
for entry in info:
|
||||
type_ = entry["type"]
|
||||
if type_ == "dynamic_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getDynamicParam(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "dynamic2_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getDynamicParam2(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "static_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getStaticParam(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "map_unit_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getMapUnitParam(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "aitree_variable":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getAITreeVariable(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "call":
|
||||
fn_name: str = entry["fn"]
|
||||
if fn_name.startswith("_ZN") and fn_name.endswith("11loadParams_Ev"):
|
||||
parent_class_name = cxxfilt.demangle(fn_name).split("::")[-2]
|
||||
out.append(f"{parent_class_name}::loadParams_();")
|
||||
else:
|
||||
out.append(f"// FIXME: CALL {fn_name} @ {entry['addr']:#x}")
|
||||
else:
|
||||
raise AssertionError(f"unknown type: {type_}")
|
||||
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def generate_action_param_member_vars(parent: str, info: list) -> str:
|
||||
out = []
|
||||
|
||||
# Ignore duplicate calls to getXXXXXParam
|
||||
params_dict = dict()
|
||||
for entry in info:
|
||||
offset: Optional[int] = entry.get("param_offset")
|
||||
if offset is not None:
|
||||
params_dict[offset] = entry
|
||||
params = list(params_dict.values())
|
||||
params.sort(key=lambda entry: entry["param_offset"])
|
||||
|
||||
if not parent and params:
|
||||
first_offset: int = params[0]["param_offset"]
|
||||
sizeof_action = 0x20
|
||||
diff = first_offset - sizeof_action
|
||||
assert diff >= 0
|
||||
if diff > 0:
|
||||
out.append(f"// FIXME: remove this")
|
||||
out.append(f"u8 pad_0x20[{diff:#x}];")
|
||||
|
||||
for entry in params:
|
||||
if not entry["param_name"]:
|
||||
continue
|
||||
out.append(f"// {entry['type']} at offset {entry['param_offset']:#x}")
|
||||
out.append(f"{entry['param_type']} {get_member_name(entry)}{{}};")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
@enum.unique
|
||||
class CommonVIndex(enum.IntEnum):
|
||||
Dtor = 2
|
||||
OneShot = 10
|
||||
Init = 11
|
||||
Enter = 12
|
||||
Leave = 14
|
||||
LoadParams = 15
|
||||
Calc = 31
|
||||
|
||||
|
||||
def generate_action(class_dir: Path, name: str, info: list, parent: str, seen_virtual_functions: Set[int],
|
||||
vtable: int) -> None:
|
||||
name = name[0].upper() + name[1:]
|
||||
if parent:
|
||||
parent = parent[0].upper() + parent[1:]
|
||||
|
||||
cpp_class_name = f"{name}"
|
||||
header_file_name = f"action{name}.h"
|
||||
|
||||
parent_class_name = parent if parent else 'ksys::act::ai::Action'
|
||||
|
||||
own_virtual_functions: Set[int] = set()
|
||||
for i, fn in enumerate(elf.get_vtable_fns_from_base_elf(vtable, 32)):
|
||||
if i not in CommonVIndex.__members__.values() or fn in seen_virtual_functions:
|
||||
continue
|
||||
own_virtual_functions.add(i)
|
||||
seen_virtual_functions.add(fn)
|
||||
|
||||
# Header
|
||||
out = []
|
||||
out.append("#pragma once")
|
||||
out.append("")
|
||||
if parent:
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, f"action{parent}.h")}"')
|
||||
out.append('#include "KingSystem/ActorSystem/actAiAction.h"')
|
||||
out.append("")
|
||||
out.append("namespace uking::action {")
|
||||
out.append("")
|
||||
out.append(f"class {cpp_class_name} : public {parent_class_name} {{")
|
||||
out.append(f" SEAD_RTTI_OVERRIDE({cpp_class_name}, {parent_class_name})")
|
||||
out.append("public:")
|
||||
out.append(f" explicit {cpp_class_name}(const InitArg& arg);")
|
||||
if CommonVIndex.Dtor in own_virtual_functions:
|
||||
out.append(f" ~{cpp_class_name}() override;")
|
||||
out.append("")
|
||||
if CommonVIndex.Init in own_virtual_functions:
|
||||
out.append(" bool init_(sead::Heap* heap) override;")
|
||||
if CommonVIndex.Enter in own_virtual_functions:
|
||||
out.append(" void enter_(ksys::act::ai::InlineParamPack* params) override;")
|
||||
if CommonVIndex.Leave in own_virtual_functions:
|
||||
out.append(" void leave_() override;")
|
||||
if CommonVIndex.LoadParams in own_virtual_functions:
|
||||
out.append(" void loadParams_() override;")
|
||||
out.append("")
|
||||
out.append("protected:")
|
||||
if CommonVIndex.Calc in own_virtual_functions:
|
||||
out.append(" void calc_() override;")
|
||||
out.append("")
|
||||
out.append(textwrap.indent(generate_action_param_member_vars(parent, info), " " * 4))
|
||||
out.append("};") # =================================== end of class
|
||||
out.append("")
|
||||
out.append("} // namespace uking::action")
|
||||
out.append("")
|
||||
(class_dir / header_file_name).write_text("\n".join(out))
|
||||
|
||||
# .cpp
|
||||
out = []
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, header_file_name)}"')
|
||||
out.append("")
|
||||
out.append("namespace uking::action {")
|
||||
out.append("")
|
||||
out.append(f"{cpp_class_name}::{cpp_class_name}(const InitArg& arg) : {parent_class_name}(arg) {{}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Dtor in own_virtual_functions:
|
||||
out.append(f"{cpp_class_name}::~{cpp_class_name}() = default;")
|
||||
out.append("")
|
||||
if CommonVIndex.Init in own_virtual_functions:
|
||||
out.append(f"bool {cpp_class_name}::init_(sead::Heap* heap) {{")
|
||||
out.append(f" return {parent_class_name}::init_(heap);")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Enter in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::enter_(ksys::act::ai::InlineParamPack* params) {{")
|
||||
out.append(f" {parent_class_name}::enter_(params);")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Leave in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::leave_() {{")
|
||||
out.append(f" {parent_class_name}::leave_();")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.LoadParams in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::loadParams_() {{")
|
||||
out.append(textwrap.indent(generate_action_loadparam_body(info), " " * 4))
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Calc in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::calc_() {{")
|
||||
out.append(f" {parent_class_name}::calc_();")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
out.append("} // namespace uking::action")
|
||||
out.append("")
|
||||
(class_dir / f"action{name}.cpp").write_text("\n".join(out))
|
||||
|
||||
|
||||
def generate_action_factories(class_dir: Path, actions: Iterable[str]) -> None:
|
||||
out = []
|
||||
out.append("""\
|
||||
// DO NOT MAKE MAJOR EDITS. This file is automatically generated.
|
||||
// For major edits, please edit the generator script (ai_generate_queries.py) instead.
|
||||
// If edits are made to this file, make sure they are not lost when the generator is re-run.
|
||||
""")
|
||||
out.append('#include "Game/Actor/aiActionFactories.h"')
|
||||
out.append('#include <array>')
|
||||
for name in actions:
|
||||
name = name[0].upper() + name[1:]
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, f"action{name}.h")}"')
|
||||
out.append('#include "KingSystem/ActorSystem/actAiAction.h"')
|
||||
out.append('')
|
||||
out.append('namespace uking {')
|
||||
out.append('')
|
||||
out.append('using Factory = ksys::act::ai::ActionFactory;')
|
||||
out.append('')
|
||||
out.append('static Factory sActionFactories[] = {')
|
||||
for name in sorted(actions, key=lambda name: zlib.crc32(name.encode())):
|
||||
class_name = "action::" + name[0].upper() + name[1:]
|
||||
out.append(f' {{0x{zlib.crc32(name.encode()):08x}, Factory::make<{class_name}>}},')
|
||||
out.append('};')
|
||||
out.append('')
|
||||
out.append('void initActionFactories() {')
|
||||
out.append(' ksys::act::ai::Actions::setFactories(std::size(sActionFactories), sActionFactories);')
|
||||
out.append('}')
|
||||
out.append('')
|
||||
out.append('} // namespace uking')
|
||||
(class_dir.parent / f"aiActionFactories.cpp").write_text("\n".join(out))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
src_root = Path(__file__).parent.parent
|
||||
class_dir = src_root / "src" / "Game" / "Actor" / "Action"
|
||||
class_dir.mkdir(exist_ok=True)
|
||||
|
||||
action_vtables: Dict[str, List[int]] = ai_common.get_vtables()["Action"]
|
||||
action_params = ai_common.get_action_params()
|
||||
vtable_names = ai_common.get_action_vtable_names()
|
||||
|
||||
seen_virtual_functions = set()
|
||||
seen_virtual_functions.update(elf.get_vtable_fns_from_base_elf(0x24d8d68, 31))
|
||||
seen_virtual_functions.update(elf.get_vtable_fns_from_base_elf(0x25129f0, 32))
|
||||
|
||||
generated = set()
|
||||
for vtables in action_vtables.values():
|
||||
vtables = list(dict.fromkeys(vtables))
|
||||
for i in range(len(vtables)):
|
||||
# This skips the first base class.
|
||||
if i == 0:
|
||||
continue
|
||||
|
||||
vtable_parent = vtables[i - 1]
|
||||
vtable = vtables[i]
|
||||
|
||||
# This skips any other base class.
|
||||
if vtable in ai_common.BaseClasses:
|
||||
continue
|
||||
|
||||
action_name = vtable_names[vtable]
|
||||
parent_name = vtable_names[vtable_parent]
|
||||
if vtable_parent in ai_common.BaseClasses:
|
||||
parent_name = ""
|
||||
|
||||
if vtable not in generated:
|
||||
generated.add(vtable)
|
||||
generate_action(class_dir, action_name, action_params[action_name], parent_name, seen_virtual_functions,
|
||||
vtable)
|
||||
|
||||
generate_action_factories(class_dir, action_vtables.keys())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,273 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import enum
|
||||
|
||||
import cxxfilt
|
||||
import zlib
|
||||
from typing import List, Dict, Iterable, Optional, Set
|
||||
|
||||
from pathlib import Path
|
||||
import textwrap
|
||||
import ai_common
|
||||
from common.util import elf
|
||||
|
||||
|
||||
def get_member_name(entry) -> str:
|
||||
type_ = entry["type"]
|
||||
if type_ == "dynamic_param":
|
||||
return f'm{entry["param_name"]}_d'
|
||||
elif type_ == "dynamic2_param":
|
||||
return f'm{entry["param_name"]}_d'
|
||||
elif type_ == "static_param":
|
||||
return f'm{entry["param_name"]}_s'
|
||||
elif type_ == "map_unit_param":
|
||||
return f'm{entry["param_name"]}_m'
|
||||
elif type_ == "aitree_variable":
|
||||
return f'm{entry["param_name"]}_a'
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
def generate_ai_loadparam_body(info: list) -> str:
|
||||
out = []
|
||||
for entry in info:
|
||||
type_ = entry["type"]
|
||||
if type_ == "dynamic_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getDynamicParam(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "dynamic2_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getDynamicParam2(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "static_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getStaticParam(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "map_unit_param":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getMapUnitParam(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "aitree_variable":
|
||||
if entry["param_name"]:
|
||||
out.append(f'getAITreeVariable(&{get_member_name(entry)}, "{entry["param_name"]}");')
|
||||
elif type_ == "call":
|
||||
fn_name: str = entry["fn"]
|
||||
if fn_name.startswith("_ZN") and fn_name.endswith("11loadParams_Ev"):
|
||||
parent_class_name = cxxfilt.demangle(fn_name).split("::")[-2]
|
||||
out.append(f"{parent_class_name}::loadParams_();")
|
||||
else:
|
||||
out.append(f"// FIXME: CALL {fn_name} @ {entry['addr']:#x}")
|
||||
else:
|
||||
raise AssertionError(f"unknown type: {type_}")
|
||||
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def generate_ai_param_member_vars(parent: str, info: list) -> str:
|
||||
out = []
|
||||
|
||||
# Ignore duplicate calls to getXXXXXParam
|
||||
params_dict = dict()
|
||||
for entry in info:
|
||||
offset: Optional[int] = entry.get("param_offset")
|
||||
if offset is not None:
|
||||
params_dict[offset] = entry
|
||||
params = list(params_dict.values())
|
||||
params.sort(key=lambda entry: entry["param_offset"])
|
||||
|
||||
if not parent and params:
|
||||
first_offset: int = params[0]["param_offset"]
|
||||
sizeof_ai = 0x38
|
||||
diff = first_offset - sizeof_ai
|
||||
assert diff >= 0
|
||||
if diff > 0:
|
||||
out.append(f"// FIXME: remove this")
|
||||
out.append(f"u8 pad_0x38[{diff:#x}];")
|
||||
|
||||
for entry in params:
|
||||
if not entry["param_name"]:
|
||||
continue
|
||||
out.append(f"// {entry['type']} at offset {entry['param_offset']:#x}")
|
||||
out.append(f"{entry['param_type']} {get_member_name(entry)}{{}};")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
@enum.unique
|
||||
class CommonVIndex(enum.IntEnum):
|
||||
Dtor = 2
|
||||
OneShot = 10
|
||||
Init = 11
|
||||
Enter = 12
|
||||
Leave = 14
|
||||
LoadParams = 15
|
||||
Calc = 32
|
||||
|
||||
|
||||
def generate_ai(class_dir: Path, name: str, info: list, parent: str, seen_virtual_functions: Set[int],
|
||||
vtable: int) -> None:
|
||||
name = name[0].upper() + name[1:]
|
||||
if parent:
|
||||
parent = parent[0].upper() + parent[1:]
|
||||
|
||||
cpp_class_name = f"{name}"
|
||||
header_file_name = f"ai{name}.h"
|
||||
|
||||
parent_class_name = parent if parent else 'ksys::act::ai::Ai'
|
||||
|
||||
own_virtual_functions: Set[int] = set()
|
||||
for i, fn in enumerate(elf.get_vtable_fns_from_base_elf(vtable, 32)):
|
||||
if i not in CommonVIndex.__members__.values() or fn in seen_virtual_functions:
|
||||
continue
|
||||
own_virtual_functions.add(i)
|
||||
seen_virtual_functions.add(fn)
|
||||
|
||||
# Header
|
||||
out = []
|
||||
out.append("#pragma once")
|
||||
out.append("")
|
||||
if parent:
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, f"ai{parent}.h")}"')
|
||||
out.append('#include "KingSystem/ActorSystem/actAiAi.h"')
|
||||
out.append("")
|
||||
out.append("namespace uking::ai {")
|
||||
out.append("")
|
||||
out.append(f"class {cpp_class_name} : public {parent_class_name} {{")
|
||||
out.append(f" SEAD_RTTI_OVERRIDE({cpp_class_name}, {parent_class_name})")
|
||||
out.append("public:")
|
||||
out.append(f" explicit {cpp_class_name}(const InitArg& arg);")
|
||||
if CommonVIndex.Dtor in own_virtual_functions:
|
||||
out.append(f" ~{cpp_class_name}() override;")
|
||||
out.append("")
|
||||
if CommonVIndex.Init in own_virtual_functions:
|
||||
out.append(" bool init_(sead::Heap* heap) override;")
|
||||
if CommonVIndex.Enter in own_virtual_functions:
|
||||
out.append(" void enter_(ksys::act::ai::InlineParamPack* params) override;")
|
||||
if CommonVIndex.Leave in own_virtual_functions:
|
||||
out.append(" void leave_() override;")
|
||||
if CommonVIndex.LoadParams in own_virtual_functions:
|
||||
out.append(" void loadParams_() override;")
|
||||
out.append("")
|
||||
out.append("protected:")
|
||||
if CommonVIndex.Calc in own_virtual_functions:
|
||||
out.append(" void calc_() override;")
|
||||
out.append("")
|
||||
out.append(textwrap.indent(generate_ai_param_member_vars(parent, info), " " * 4))
|
||||
out.append("};") # =================================== end of class
|
||||
out.append("")
|
||||
out.append("} // namespace uking::ai")
|
||||
out.append("")
|
||||
(class_dir / header_file_name).write_text("\n".join(out))
|
||||
|
||||
# .cpp
|
||||
out = []
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, header_file_name)}"')
|
||||
out.append("")
|
||||
out.append("namespace uking::ai {")
|
||||
out.append("")
|
||||
out.append(f"{cpp_class_name}::{cpp_class_name}(const InitArg& arg) : {parent_class_name}(arg) {{}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Dtor in own_virtual_functions:
|
||||
out.append(f"{cpp_class_name}::~{cpp_class_name}() = default;")
|
||||
out.append("")
|
||||
if CommonVIndex.Init in own_virtual_functions:
|
||||
out.append(f"bool {cpp_class_name}::init_(sead::Heap* heap) {{")
|
||||
out.append(f" return {parent_class_name}::init_(heap);")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Enter in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::enter_(ksys::act::ai::InlineParamPack* params) {{")
|
||||
out.append(f" {parent_class_name}::enter_(params);")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Leave in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::leave_() {{")
|
||||
out.append(f" {parent_class_name}::leave_();")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.LoadParams in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::loadParams_() {{")
|
||||
out.append(textwrap.indent(generate_ai_loadparam_body(info), " " * 4))
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
if CommonVIndex.Calc in own_virtual_functions:
|
||||
out.append(f"void {cpp_class_name}::calc_() {{")
|
||||
out.append(f" {parent_class_name}::calc_();")
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
out.append("} // namespace uking::ai")
|
||||
out.append("")
|
||||
(class_dir / f"ai{name}.cpp").write_text("\n".join(out))
|
||||
|
||||
|
||||
def generate_ai_factories(class_dir: Path, ais: Iterable[str]) -> None:
|
||||
out = []
|
||||
out.append("""\
|
||||
// DO NOT MAKE MAJOR EDITS. This file is automatically generated.
|
||||
// For major edits, please edit the generator script (ai_generate_ais.py) instead.
|
||||
// If edits are made to this file, make sure they are not lost when the generator is re-run.
|
||||
""")
|
||||
out.append('#include "Game/Actor/aiAiFactories.h"')
|
||||
out.append('#include <array>')
|
||||
for name in ais:
|
||||
name = name[0].upper() + name[1:]
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, f"ai{name}.h")}"')
|
||||
out.append('#include "KingSystem/ActorSystem/actAiAi.h"')
|
||||
out.append('')
|
||||
out.append('namespace uking {')
|
||||
out.append('')
|
||||
out.append('using Factory = ksys::act::ai::AiFactory;')
|
||||
out.append('')
|
||||
out.append('static Factory sAiFactories[] = {')
|
||||
for name in sorted(ais, key=lambda name: zlib.crc32(name.encode())):
|
||||
class_name = "ai::" + name[0].upper() + name[1:]
|
||||
out.append(f' {{0x{zlib.crc32(name.encode()):08x}, Factory::make<{class_name}>}},')
|
||||
out.append('};')
|
||||
out.append('')
|
||||
out.append('void initAiFactories() {')
|
||||
out.append(' ksys::act::ai::Ais::setFactories(std::size(sAiFactories), sAiFactories);')
|
||||
out.append('}')
|
||||
out.append('')
|
||||
out.append('} // namespace uking')
|
||||
(class_dir.parent / f"aiAiFactories.cpp").write_text("\n".join(out))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
src_root = Path(__file__).parent.parent
|
||||
class_dir = src_root / "src" / "Game" / "Actor" / "AI"
|
||||
class_dir.mkdir(exist_ok=True)
|
||||
|
||||
ai_vtables: Dict[str, List[int]] = ai_common.get_vtables()["AI"]
|
||||
ai_params = ai_common.get_ai_params()
|
||||
vtable_names = ai_common.get_ai_vtable_names()
|
||||
|
||||
seen_virtual_functions = set()
|
||||
# ai::ActionBase
|
||||
seen_virtual_functions.update(elf.get_vtable_fns_from_base_elf(0x24d8d68, 31))
|
||||
# ai::Ai
|
||||
seen_virtual_functions.update(elf.get_vtable_fns_from_base_elf(0x2513278, 34))
|
||||
|
||||
generated = set()
|
||||
for vtables in ai_vtables.values():
|
||||
vtables = list(dict.fromkeys(vtables))
|
||||
for i in range(len(vtables)):
|
||||
# This skips the first base class.
|
||||
if i == 0:
|
||||
continue
|
||||
|
||||
vtable_parent = vtables[i - 1]
|
||||
vtable = vtables[i]
|
||||
|
||||
# This skips any other base class.
|
||||
if vtable in ai_common.BaseClasses:
|
||||
continue
|
||||
|
||||
ai_name = vtable_names[vtable]
|
||||
parent_name = vtable_names[vtable_parent]
|
||||
if vtable_parent in ai_common.BaseClasses:
|
||||
parent_name = ""
|
||||
|
||||
if vtable not in generated:
|
||||
generated.add(vtable)
|
||||
generate_ai(class_dir, ai_name, ai_params[ai_name], parent_name, seen_virtual_functions, vtable)
|
||||
|
||||
generate_ai_factories(class_dir, ai_vtables.keys())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,213 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import zlib
|
||||
from typing import List
|
||||
|
||||
import oead
|
||||
from pathlib import Path
|
||||
import textwrap
|
||||
|
||||
|
||||
def sort_params(params: list) -> list:
|
||||
def sort_by_type(param):
|
||||
t = param["Type"]
|
||||
if t == "String":
|
||||
return 1
|
||||
return 0
|
||||
|
||||
return sorted(params, key=sort_by_type)
|
||||
|
||||
|
||||
def generate_query_loadparam_body(query: dict, is_evfl: bool) -> str:
|
||||
if not query:
|
||||
return ""
|
||||
|
||||
out = []
|
||||
|
||||
if is_evfl:
|
||||
for param in sort_params(query.get("DynamicInstParams", [])):
|
||||
out.append(f"load{param['Type']}(arg.param_accessor, \"{param['Name']}\");")
|
||||
else:
|
||||
for param in sort_params(query.get("StaticInstParams", [])):
|
||||
out.append(f"getStaticParam(&m{param['Name']}, \"{param['Name']}\");")
|
||||
|
||||
for param in sort_params(query.get("DynamicInstParams", [])):
|
||||
out.append(f"getDynamicParam(&m{param['Name']}, \"{param['Name']}\");")
|
||||
|
||||
for param in sort_params(query.get("AITreeVariables", [])):
|
||||
out.append(f"getAITreeVariable(&m{param['Name']}, \"{param['Name']}\");")
|
||||
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
_types_static = {
|
||||
"Bool": "const bool*",
|
||||
"Int": "const int*",
|
||||
"Float": "const float*",
|
||||
"String": "sead::SafeString",
|
||||
}
|
||||
|
||||
_types_dynamic = {
|
||||
"Bool": "bool*",
|
||||
"Int": "int*",
|
||||
"Float": "float*",
|
||||
"String": "sead::SafeString",
|
||||
}
|
||||
|
||||
_types_ai_tree_var = {
|
||||
"String": "sead::SafeString*",
|
||||
"AITreeVariablePointer": "void*",
|
||||
}
|
||||
|
||||
|
||||
def generate_query_param_member_vars(query: dict) -> str:
|
||||
out = []
|
||||
|
||||
for param in sort_params(query.get("StaticInstParams", [])):
|
||||
out.append(f"{_types_static[param['Type']]} m{param['Name']}{{}};")
|
||||
|
||||
for param in sort_params(query.get("DynamicInstParams", [])):
|
||||
out.append(f"{_types_dynamic[param['Type']]} m{param['Name']}{{}};")
|
||||
|
||||
for param in sort_params(query.get("AITreeVariables", [])):
|
||||
out.append(f"{_types_ai_tree_var[param['Type']]} m{param['Name']}{{}};")
|
||||
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def generate_query(class_dir: Path, name: str, query) -> None:
|
||||
has_params = False
|
||||
if query != "":
|
||||
assert isinstance(query, oead.byml.Hash)
|
||||
query = dict(query)
|
||||
has_params = "DynamicInstParams" in query or "StaticInstParams" in query or "AITreeVariables" in query
|
||||
|
||||
cpp_class_name = f"{name}"
|
||||
header_file_name = f"query{name}.h"
|
||||
|
||||
# Header
|
||||
out = []
|
||||
out.append("#pragma once")
|
||||
out.append("")
|
||||
out.append('#include "KingSystem/ActorSystem/actAiQuery.h"')
|
||||
out.append("")
|
||||
out.append("namespace uking::query {")
|
||||
out.append("")
|
||||
out.append(f"class {cpp_class_name} : public ksys::act::ai::Query {{")
|
||||
out.append(f" SEAD_RTTI_OVERRIDE({cpp_class_name}, Query)")
|
||||
out.append("public:")
|
||||
out.append(f" explicit {cpp_class_name}(const InitArg& arg);")
|
||||
out.append(f" ~{cpp_class_name}() override;")
|
||||
out.append(f" int doQuery() override;")
|
||||
out.append("")
|
||||
out.append(" void loadParams() override;")
|
||||
out.append(" void loadParams(const evfl::QueryArg& arg) override;")
|
||||
if has_params:
|
||||
out.append("")
|
||||
out.append("protected:")
|
||||
out.append(textwrap.indent(generate_query_param_member_vars(query), " " * 4))
|
||||
out.append("};") # =================================== end of class
|
||||
out.append("")
|
||||
out.append("} // namespace uking::query")
|
||||
out.append("")
|
||||
(class_dir / header_file_name).write_text("\n".join(out))
|
||||
|
||||
# .cpp
|
||||
out = []
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, header_file_name)}"')
|
||||
out.append(f'#include <evfl/query.h>')
|
||||
out.append("")
|
||||
out.append("namespace uking::query {")
|
||||
out.append("")
|
||||
out.append(f"{cpp_class_name}::{cpp_class_name}(const InitArg& arg) : ksys::act::ai::Query(arg) {{}}")
|
||||
out.append("")
|
||||
out.append(f"{cpp_class_name}::~{cpp_class_name}() = default;")
|
||||
out.append("")
|
||||
out.append("// FIXME: implement")
|
||||
out.append(f"int {cpp_class_name}::doQuery() {{ return -1; }}")
|
||||
out.append("")
|
||||
out.append(f"void {cpp_class_name}::loadParams(const evfl::QueryArg& arg) {{")
|
||||
out.append(textwrap.indent(generate_query_loadparam_body(query, is_evfl=True), " " * 4))
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
out.append(f"void {cpp_class_name}::loadParams() {{")
|
||||
out.append(textwrap.indent(generate_query_loadparam_body(query, is_evfl=False), " " * 4))
|
||||
out.append(f"}}")
|
||||
out.append("")
|
||||
out.append("} // namespace uking::query")
|
||||
out.append("")
|
||||
(class_dir / f"query{name}.cpp").write_text("\n".join(out))
|
||||
|
||||
|
||||
def generate_query_factories(class_dir: Path, aidef) -> None:
|
||||
queries: List[str] = []
|
||||
for query_name, data in aidef["Querys"].items():
|
||||
if isinstance(data, oead.byml.Hash) and dict(data).get("SystemQuery", False):
|
||||
continue
|
||||
queries.append(query_name)
|
||||
|
||||
out = []
|
||||
out.append("""\
|
||||
// DO NOT MAKE MAJOR EDITS. This file is automatically generated.
|
||||
// For major edits, please edit the generator script (ai_generate_queries.py) instead.
|
||||
// If edits are made to this file, make sure they are not lost when the generator is re-run.
|
||||
""")
|
||||
out.append('#include "Game/Actor/aiQueryFactories.h"')
|
||||
out.append('#include <array>')
|
||||
for query_name in queries:
|
||||
query_name = query_name[0].upper() + query_name[1:]
|
||||
out.append(f'#include "{ai_common.find_header(class_dir, f"query{query_name}.h")}"')
|
||||
out.append('#include "KingSystem/ActorSystem/actAiQueries.h"')
|
||||
out.append('#include "KingSystem/ActorSystem/actAiQuery.h"')
|
||||
out.append('')
|
||||
out.append('namespace uking {')
|
||||
out.append('')
|
||||
out.append('using Factory = ksys::act::ai::QueryFactory;')
|
||||
out.append('')
|
||||
out.append('static ksys::act::ai::QueryFactory sQueryFactories[] = {')
|
||||
for query_name in sorted(queries, key=lambda query: zlib.crc32(query.encode())):
|
||||
class_name = "query::" + query_name[0].upper() + query_name[1:]
|
||||
out.append(f' {{0x{zlib.crc32(query_name.encode()):08x}, Factory::make<{class_name}>}},')
|
||||
out.append('};')
|
||||
out.append('')
|
||||
out.append('void initQueryFactories() {')
|
||||
out.append(' ksys::act::ai::Queries::setFactories(std::size(sQueryFactories), sQueryFactories);')
|
||||
out.append('}')
|
||||
out.append('')
|
||||
out.append('} // namespace uking')
|
||||
(class_dir.parent / f"aiQueryFactories.cpp").write_text("\n".join(out))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
src_root = Path(__file__).parent.parent
|
||||
class_dir = src_root / "src" / "Game" / "Actor" / "Query"
|
||||
class_dir.mkdir(exist_ok=True)
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generates stubs for AI queries.")
|
||||
parser.add_argument("aidef")
|
||||
args = parser.parse_args()
|
||||
|
||||
aidef = oead.byml.from_text(Path(args.aidef).read_text(encoding="utf-8"))
|
||||
|
||||
count = 0
|
||||
keys = set()
|
||||
for query_name, data in aidef["Querys"].items():
|
||||
if isinstance(data, oead.byml.Hash) and dict(data).get("SystemQuery", False):
|
||||
continue
|
||||
|
||||
if isinstance(data, oead.byml.Hash):
|
||||
keys |= set(data.keys())
|
||||
|
||||
query_name = query_name[0].upper() + query_name[1:]
|
||||
generate_query(class_dir, query_name, data)
|
||||
print(query_name)
|
||||
count += 1
|
||||
|
||||
generate_query_factories(class_dir, aidef)
|
||||
|
||||
print()
|
||||
print(f"{count} queries")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,133 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
|
||||
import oead
|
||||
from colorama import Fore
|
||||
import cxxfilt
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable
|
||||
|
||||
import common.util.checker
|
||||
import common.util.elf
|
||||
from common.util import utils
|
||||
import ai_common
|
||||
|
||||
|
||||
def identify(functions: Dict[str, utils.FunctionInfo], checker: common.util.checker.FunctionChecker,
|
||||
new_matches: Dict[int, str], class_names: Iterable[str], get_pairs) -> None:
|
||||
for name in class_names:
|
||||
orig_name = name
|
||||
name = name[0].upper() + name[1:]
|
||||
|
||||
pairs = get_pairs(orig_name, name)
|
||||
for orig_fn_name, fn_name in pairs:
|
||||
orig_fn_info = functions.get(orig_fn_name, None)
|
||||
if orig_fn_info is None:
|
||||
continue
|
||||
if orig_fn_info.status != utils.FunctionStatus.NotDecompiled:
|
||||
continue
|
||||
|
||||
orig_fn = common.util.elf.get_fn_from_base_elf(orig_fn_info.addr, orig_fn_info.size)
|
||||
try:
|
||||
decomp_fn = common.util.elf.get_fn_from_my_elf(fn_name)
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
if checker.check(orig_fn, decomp_fn):
|
||||
new_matches[orig_fn_info.addr] = fn_name
|
||||
utils.print_note(f"new match: {Fore.BLUE}{cxxfilt.demangle(fn_name)}{Fore.RESET}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Identifies matching AI class functions.")
|
||||
parser.add_argument("aidef")
|
||||
parser.add_argument("--type", choices=["Action", "AI", "Behavior", "Query"], required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
type_: str = args.type
|
||||
|
||||
new_matches: Dict[int, str] = dict()
|
||||
checker = common.util.checker.FunctionChecker()
|
||||
functions: Dict[str, utils.FunctionInfo] = {fn.name: fn for fn in utils.get_functions()}
|
||||
|
||||
aidef = oead.byml.from_text(Path(args.aidef).read_text(encoding="utf-8"))
|
||||
|
||||
def get_query_pairs(orig_name, name):
|
||||
prefix = f"AI_Query_{orig_name}::"
|
||||
return [
|
||||
(f"{prefix}ctor", f"_ZN5uking5query{len(name)}{name}C1ERKN4ksys3act2ai5Query7InitArgE"),
|
||||
(f"{prefix}dtor", f"_ZN5uking5query{len(name)}{name}D1Ev"),
|
||||
(f"{prefix}dtorDelete", f"_ZN5uking5query{len(name)}{name}D0Ev"),
|
||||
(f"{prefix}m10", f"_ZN5uking5query{len(name)}{name}10loadParamsERKN4evfl8QueryArgE"),
|
||||
(f"{prefix}loadParams", f"_ZN5uking5query{len(name)}{name}10loadParamsEv"),
|
||||
(f"{prefix}rtti1",
|
||||
f"_ZNK5uking5query{len(name)}{name}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE"),
|
||||
(f"{prefix}rtti2", f"_ZNK5uking5query{len(name)}{name}18getRuntimeTypeInfoEv"),
|
||||
(f"AI_F_Query_{orig_name}",
|
||||
f"_ZN4ksys3act2ai12QueryFactory4makeIN5uking5query{len(name)}{name}EEEPNS1_5QueryERKNS7_7InitArgEPN4sead4HeapE"),
|
||||
]
|
||||
|
||||
def get_action_pairs(orig_name, name):
|
||||
pairs = []
|
||||
|
||||
def add_pair(x):
|
||||
pairs.append((x, x))
|
||||
|
||||
pairs.append(
|
||||
(f"AI_Action_{orig_name}::ctor",
|
||||
f"_ZN5uking6action{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
|
||||
pairs.append(
|
||||
(f"AI_Action{orig_name}::ctor",
|
||||
f"_ZN5uking6action{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
|
||||
pairs.append((f"AI_F_Action_{orig_name}",
|
||||
f"_ZN4ksys3act2ai13ActionFactory4makeIN5uking6action{len(name)}{name}EEEPNS1_6ActionERKNS1_10ActionBase7InitArgEPN4sead4HeapE"))
|
||||
add_pair(f"_ZN5uking6action{len(name)}{name}D1Ev")
|
||||
add_pair(f"_ZN5uking6action{len(name)}{name}D0Ev")
|
||||
add_pair(f"_ZN5uking6action{len(name)}{name}11loadParams_Ev")
|
||||
add_pair(f"_ZN5uking6action{len(name)}{name}5init_EPN4sead4HeapE")
|
||||
add_pair(f"_ZN5uking6action{len(name)}{name}6enter_EPN4ksys3act2ai15InlineParamPackE")
|
||||
add_pair(f"_ZN5uking6action{len(name)}{name}6leave_Ev")
|
||||
add_pair(f"_ZN5uking6action{len(name)}{name}5calc_Ev")
|
||||
add_pair(
|
||||
f"_ZNK5uking6action{len(name)}{name}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE")
|
||||
add_pair(f"_ZNK5uking6action{len(name)}{name}18getRuntimeTypeInfoEv")
|
||||
return pairs
|
||||
|
||||
def get_ai_pairs(orig_name, name):
|
||||
pairs = []
|
||||
|
||||
def add_pair(x):
|
||||
pairs.append((x, x))
|
||||
|
||||
pairs.append(
|
||||
(f"AI_AI_{orig_name}::ctor", f"_ZN5uking2ai{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
|
||||
pairs.append(
|
||||
(f"AI_AI{orig_name}::ctor", f"_ZN5uking2ai{len(name)}{name}C1ERKN4ksys3act2ai10ActionBase7InitArgE"))
|
||||
pairs.append((f"AI_F_AI_{orig_name}",
|
||||
f"_ZN4ksys3act2ai9AiFactory4makeIN5uking2ai{len(name)}{name}EEEPNS1_2AiERKNS1_10ActionBase7InitArgEPN4sead4HeapE"))
|
||||
add_pair(f"_ZN5uking2ai{len(name)}{name}D1Ev")
|
||||
add_pair(f"_ZN5uking2ai{len(name)}{name}D0Ev")
|
||||
add_pair(f"_ZN5uking2ai{len(name)}{name}11loadParams_Ev")
|
||||
add_pair(f"_ZN5uking2ai{len(name)}{name}5init_EPN4sead4HeapE")
|
||||
add_pair(f"_ZN5uking2ai{len(name)}{name}6enter_EPN4ksys3act2ai15InlineParamPackE")
|
||||
add_pair(f"_ZN5uking2ai{len(name)}{name}6leave_Ev")
|
||||
add_pair(f"_ZN5uking2ai{len(name)}{name}5calc_Ev")
|
||||
add_pair(f"_ZNK5uking2ai{len(name)}{name}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE")
|
||||
add_pair(f"_ZNK5uking2ai{len(name)}{name}18getRuntimeTypeInfoEv")
|
||||
return pairs
|
||||
|
||||
if type_ == "Action":
|
||||
action_vtable_names = ai_common.get_action_vtable_names()
|
||||
identify(functions, checker, new_matches, action_vtable_names.values(), get_action_pairs)
|
||||
if type_ == "AI":
|
||||
ai_vtable_names = ai_common.get_ai_vtable_names()
|
||||
identify(functions, checker, new_matches, ai_vtable_names.values(), get_ai_pairs)
|
||||
elif type_ == "Query":
|
||||
identify(functions, checker, new_matches, aidef["Querys"].keys(), get_query_pairs)
|
||||
|
||||
utils.add_decompiled_functions(new_matches)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,114 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Union
|
||||
|
||||
import yaml
|
||||
|
||||
import ai_common
|
||||
from ai_common import BaseClasses
|
||||
from common.util.graph import Graph
|
||||
|
||||
_known_vtables = {
|
||||
0x71024d8d68: "ActionBase",
|
||||
0x71025129f0: "Action",
|
||||
0x7102513278: "Ai",
|
||||
0x71024d8ef0: "Behavior",
|
||||
0x710243c9b8: "Query",
|
||||
}
|
||||
|
||||
|
||||
def get_name_for_vtable(vtable: Union[str, int]):
|
||||
if isinstance(vtable, str):
|
||||
return vtable
|
||||
|
||||
known_name = _known_vtables.get(vtable, None)
|
||||
if known_name is not None:
|
||||
return f"[V] {known_name}"
|
||||
|
||||
return f"[V] {vtable:#x}"
|
||||
|
||||
|
||||
def guess_vtable_names(reverse_graph: Graph):
|
||||
for u in reverse_graph.nodes:
|
||||
targets = list(reverse_graph.nodes[u])
|
||||
known_targets = list(filter(lambda x: isinstance(x, str), targets))
|
||||
if len(known_targets) == 1:
|
||||
# Leaves can be named pretty easily.
|
||||
_known_vtables[u] = known_targets[0]
|
||||
|
||||
|
||||
def build_graph(all_vtables: dict, type_: str, graph: Graph, reverse_graph: Graph):
|
||||
for name, vtables in all_vtables[type_].items():
|
||||
classes = [name] + list(reversed(vtables))
|
||||
# Each class has at least one parent, so the -1 is fine.
|
||||
for i in range(len(classes) - 1):
|
||||
from_ = classes[i]
|
||||
to_ = classes[i + 1]
|
||||
# Skip base classes to reduce noise.
|
||||
if to_ in BaseClasses:
|
||||
break
|
||||
reverse_graph.add_edge(to_, from_)
|
||||
|
||||
guess_vtable_names(reverse_graph)
|
||||
|
||||
for name, vtables in all_vtables[type_].items():
|
||||
classes = [name] + list(reversed(vtables))
|
||||
for i in range(len(classes) - 1):
|
||||
if classes[i + 1] in BaseClasses:
|
||||
break
|
||||
from_ = get_name_for_vtable(classes[i])
|
||||
to_ = get_name_for_vtable(classes[i + 1])
|
||||
graph.add_edge(from_, to_)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Shows AI classes with non-trivial class hierarchies.")
|
||||
parser.add_argument("--type", help="AI class type to visualise", choices=["Action", "AI", "Behavior", "Query"],
|
||||
required=True)
|
||||
parser.add_argument("--out-names", help="Path to which a vtable -> name map will be written", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
all_vtables = ai_common.get_vtables()
|
||||
|
||||
graph = Graph()
|
||||
reverse_graph = Graph()
|
||||
build_graph(all_vtables, args.type, graph, reverse_graph)
|
||||
|
||||
interesting_nodes = set()
|
||||
node_colors = dict()
|
||||
|
||||
colors = ["#c7dcff", "#ffc7c7", "#ceffc7", "#dcc7ff", "#fffdc9", "#c9fff3", "#ffe0cc", "#ffcffe", "#96a8ff"]
|
||||
components = graph.find_connected_components()
|
||||
num_nontrivial_cc = 0
|
||||
for i, comp in enumerate(components):
|
||||
if len(comp) == 2:
|
||||
continue
|
||||
for node in comp:
|
||||
node_colors[node] = colors[i % len(colors)]
|
||||
num_nontrivial_cc += 1
|
||||
interesting_nodes |= set(comp)
|
||||
|
||||
print("digraph {")
|
||||
print("node [shape=rectangle]")
|
||||
for u in graph.nodes:
|
||||
if u not in interesting_nodes:
|
||||
continue
|
||||
for v in graph.nodes[u]:
|
||||
shape_u = "shape=component," if "[V]" not in u else ""
|
||||
shape_v = "shape=component," if "[V]" not in v else ""
|
||||
print(f'"{u}" [{shape_u}style=filled, fillcolor="{node_colors[u]}"]')
|
||||
print(f'"{v}" [{shape_v}style=filled, fillcolor="{node_colors[v]}"]')
|
||||
print(f'"{u}" -> "{v}"')
|
||||
print("}")
|
||||
print(f"# {len(components)} connected components")
|
||||
print(f"# {num_nontrivial_cc} non-trivial connected components")
|
||||
|
||||
yaml.add_representer(int, lambda dumper, data: yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#x}"),
|
||||
Dumper=yaml.CSafeDumper)
|
||||
with Path(args.out_names).open("w") as f:
|
||||
yaml.dump(_known_vtables, f, Dumper=yaml.CSafeDumper)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,95 +0,0 @@
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from common.util import utils
|
||||
import ai_common
|
||||
import idaapi
|
||||
|
||||
from ai_common import BaseClasses
|
||||
|
||||
_vtable_fn_names = [
|
||||
"_ZNK5uking6action{}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE",
|
||||
"_ZNK5uking6action{}18getRuntimeTypeInfoEv",
|
||||
"_ZN5uking6action{}D2Ev",
|
||||
"_ZN5uking6action{}D0Ev",
|
||||
"_ZNK5uking6action{}8isFailedEv",
|
||||
"_ZNK5uking6action{}10isFinishedEv",
|
||||
"_ZNK5uking6action{}12isChangeableEv",
|
||||
"_ZN5uking6action{}14hasPreDeleteCbEv",
|
||||
"_ZN5uking6action{}23hasUpdateForPreDeleteCbEv",
|
||||
"_ZN5uking6action{}2m9Ev",
|
||||
"_ZN5uking6action{}8oneShot_Ev",
|
||||
"_ZN5uking6action{}5init_EPN4sead4HeapE",
|
||||
"_ZN5uking6action{}6enter_EPN4ksys3act2ai15InlineParamPackE",
|
||||
"_ZN5uking6action{}8reenter_EPS2_b",
|
||||
"_ZN5uking6action{}6leave_Ev",
|
||||
"_ZN5uking6action{}11loadParams_Ev",
|
||||
"_ZN5uking6action{}14handleMessage_ERKN4ksys7MessageE",
|
||||
"_ZN5uking6action{}10handleAck_ERKN4ksys10MessageAckE",
|
||||
"_ZN5uking6action{}18updateForPreDeleteEv",
|
||||
"_ZN5uking6action{}11onPreDeleteEv",
|
||||
"_ZN5uking6action{}4calcEv",
|
||||
"_ZNK5uking6action{}14getCurrentNameEPN4sead22BufferedSafeStringBaseIcEEPS2_",
|
||||
"_ZN5uking6action{}11changeChildERKN4sead14SafeStringBaseIcEE",
|
||||
"_ZNK5uking6action{}9getParamsEPN4ksys3act2ai18ParamNameTypePairsEb",
|
||||
"_ZNK5uking6action{}14getNumChildrenEv",
|
||||
"_ZN5uking6action{}12initChildrenERKN4ksys8AIDefSetEPN4sead4HeapE",
|
||||
"_ZNK5uking6action{}15getCurrentChildEv",
|
||||
"_ZNK5uking6action{}7getTypeEv",
|
||||
"_ZN5uking6action{}7reenterEPS2_RKN4sead14SafeStringBaseIcEE",
|
||||
"_ZN5uking6action{}9postLeaveEv",
|
||||
"_ZNK5uking6action{}8getChildEi",
|
||||
"_ZN5uking6action{}5calc_Ev",
|
||||
]
|
||||
|
||||
|
||||
def format_fn_name(name: str, class_name: str):
|
||||
return name.format(f"{len(class_name)}{class_name}")
|
||||
|
||||
|
||||
def iterate_vtable(vtable_addr):
|
||||
ea = vtable_addr
|
||||
while True:
|
||||
fn_ea = struct.unpack('<Q', idaapi.get_bytes(ea, 8))[0]
|
||||
if idaapi.get_name(fn_ea) != "__cxa_pure_virtual" and not idaapi.is_func(idaapi.get_flags(fn_ea)):
|
||||
return
|
||||
yield fn_ea
|
||||
ea += 8
|
||||
|
||||
|
||||
_ida_base = 0x7100000000
|
||||
|
||||
|
||||
def main() -> None:
|
||||
all_vtables = ai_common.get_vtables()
|
||||
names = ai_common.get_action_vtable_names()
|
||||
not_decompiled = {func.addr for func in utils.get_functions() if func.status == utils.FunctionStatus.NotDecompiled}
|
||||
|
||||
new_names: Dict[int, str] = dict()
|
||||
|
||||
order = ai_common.topologically_sort_vtables(all_vtables, "Action")
|
||||
for vtable_addr in order:
|
||||
if vtable_addr in BaseClasses:
|
||||
continue
|
||||
|
||||
class_name = names.get(vtable_addr)
|
||||
for i, fn_ea in enumerate(iterate_vtable(vtable_addr)):
|
||||
if idaapi.get_name(fn_ea) == "__cxa_pure_virtual":
|
||||
continue
|
||||
|
||||
real_fn_ea = fn_ea & ~_ida_base
|
||||
if real_fn_ea not in new_names:
|
||||
if i < len(_vtable_fn_names):
|
||||
new_names[real_fn_ea] = format_fn_name(_vtable_fn_names[i], class_name)
|
||||
else:
|
||||
# Unknown member function.
|
||||
new_names[real_fn_ea] = f"uking::action::{class_name}::m{i}"
|
||||
|
||||
if real_fn_ea in not_decompiled:
|
||||
idaapi.set_name(fn_ea, new_names[real_fn_ea])
|
||||
|
||||
utils.add_decompiled_functions(dict(), new_names)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,97 +0,0 @@
|
||||
import struct
|
||||
from typing import Dict
|
||||
|
||||
from common.util import utils
|
||||
import ai_common
|
||||
import idaapi
|
||||
|
||||
from ai_common import BaseClasses
|
||||
|
||||
_vtable_fn_names = [
|
||||
"_ZNK5uking2ai{}27checkDerivedRuntimeTypeInfoEPKN4sead15RuntimeTypeInfo9InterfaceE",
|
||||
"_ZNK5uking2ai{}18getRuntimeTypeInfoEv",
|
||||
"_ZN5uking2ai{}D2Ev",
|
||||
"_ZN5uking2ai{}D0Ev",
|
||||
"_ZNK5uking2ai{}8isFailedEv",
|
||||
"_ZNK5uking2ai{}10isFinishedEv",
|
||||
"_ZNK5uking2ai{}12isChangeableEv",
|
||||
"_ZN5uking2ai{}14hasPreDeleteCbEv",
|
||||
"_ZN5uking2ai{}23hasUpdateForPreDeleteCbEv",
|
||||
"_ZN5uking2ai{}2m9Ev",
|
||||
"_ZN5uking2ai{}8oneShot_Ev",
|
||||
"_ZN5uking2ai{}5init_EPN4sead4HeapE",
|
||||
"_ZN5uking2ai{}6enter_EPN4ksys3act2ai15InlineParamPackE",
|
||||
"_ZN5uking2ai{}8reenter_EPS2_b",
|
||||
"_ZN5uking2ai{}6leave_Ev",
|
||||
"_ZN5uking2ai{}11loadParams_Ev",
|
||||
"_ZN5uking2ai{}14handleMessage_ERKN4ksys7MessageE",
|
||||
"_ZN5uking2ai{}10handleAck_ERKN4ksys10MessageAckE",
|
||||
"_ZN5uking2ai{}18updateForPreDeleteEv",
|
||||
"_ZN5uking2ai{}11onPreDeleteEv",
|
||||
"_ZN5uking2ai{}4calcEv",
|
||||
"_ZNK5uking2ai{}14getCurrentNameEPN4sead22BufferedSafeStringBaseIcEEPS2_",
|
||||
"_ZN5uking2ai{}11changeChildERKN4sead14SafeStringBaseIcEE",
|
||||
"_ZNK5uking2ai{}9getParamsEPN4ksys3act2ai18ParamNameTypePairsEb",
|
||||
"_ZNK5uking2ai{}14getNumChildrenEv",
|
||||
"_ZN5uking2ai{}12initChildrenERKN4ksys8AIDefSetEPN4sead4HeapE",
|
||||
"_ZNK5uking2ai{}15getCurrentChildEv",
|
||||
"_ZNK5uking2ai{}7getTypeEv",
|
||||
"_ZN5uking2ai{}7reenterEPS2_RKN4sead14SafeStringBaseIcEE",
|
||||
"_ZN5uking2ai{}9postLeaveEv",
|
||||
"_ZNK5uking2ai{}8getChildEi",
|
||||
"_ZNK5uking2ai{}8getNamesEPN4sead22BufferedSafeStringBaseIcEE",
|
||||
"_ZN5uking2ai{}5calc_Ev",
|
||||
"_ZN5uking2ai{}25handlePendingChildChange_Ev",
|
||||
]
|
||||
|
||||
|
||||
def format_fn_name(name: str, class_name: str):
|
||||
return name.format(f"{len(class_name)}{class_name}")
|
||||
|
||||
|
||||
def iterate_vtable(vtable_addr):
|
||||
ea = vtable_addr
|
||||
while True:
|
||||
fn_ea = struct.unpack('<Q', idaapi.get_bytes(ea, 8))[0]
|
||||
if idaapi.get_name(fn_ea) != "__cxa_pure_virtual" and not idaapi.is_func(idaapi.get_flags(fn_ea)):
|
||||
return
|
||||
yield fn_ea
|
||||
ea += 8
|
||||
|
||||
|
||||
_ida_base = 0x7100000000
|
||||
|
||||
|
||||
def main() -> None:
|
||||
all_vtables = ai_common.get_vtables()
|
||||
names = ai_common.get_ai_vtable_names()
|
||||
not_decompiled = {func.addr for func in utils.get_functions() if func.status == utils.FunctionStatus.NotDecompiled}
|
||||
|
||||
new_names: Dict[int, str] = dict()
|
||||
|
||||
order = ai_common.topologically_sort_vtables(all_vtables, "AI")
|
||||
for vtable_addr in order:
|
||||
if vtable_addr in BaseClasses:
|
||||
continue
|
||||
|
||||
class_name = names.get(vtable_addr)
|
||||
for i, fn_ea in enumerate(iterate_vtable(vtable_addr)):
|
||||
if idaapi.get_name(fn_ea) == "__cxa_pure_virtual":
|
||||
continue
|
||||
|
||||
real_fn_ea = fn_ea & ~_ida_base
|
||||
if real_fn_ea not in new_names:
|
||||
if i < len(_vtable_fn_names):
|
||||
new_names[real_fn_ea] = format_fn_name(_vtable_fn_names[i], class_name)
|
||||
else:
|
||||
# Unknown member function.
|
||||
new_names[real_fn_ea] = f"uking::ai::{class_name}::m{i}"
|
||||
|
||||
if real_fn_ea in not_decompiled:
|
||||
idaapi.set_name(fn_ea, new_names[real_fn_ea])
|
||||
|
||||
utils.add_decompiled_functions(dict(), new_names)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user