Merge pull request #218 from leoetlino/stack/03-drop-ai-generators

tools: drop the AI class generators and their aidef data
This commit is contained in:
Léo Lam
2026-10-08 01:12:37 +01:00
committed by GitHub
16 changed files with 0 additions and 21594 deletions
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
-4
View File
@@ -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"
-4
View File
@@ -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"
-4
View File
@@ -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"
-74
View File
@@ -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
-272
View File
@@ -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()
-273
View File
@@ -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()
-213
View File
@@ -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()
-133
View File
@@ -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()
-114
View File
@@ -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()
-95
View File
@@ -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()
-97
View File
@@ -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()