diff options
Diffstat (limited to 'tools/vtable_sym.py')
| -rw-r--r-- | tools/vtable_sym.py | 129 |
1 files changed, 129 insertions, 0 deletions
diff --git a/tools/vtable_sym.py b/tools/vtable_sym.py new file mode 100644 index 00000000..826cd610 --- /dev/null +++ b/tools/vtable_sym.py @@ -0,0 +1,129 @@ +import argparse +from pathlib import Path +import re + +def main(): + parser = argparse.ArgumentParser(description='Define vtable symbols and update relocations') + parser.add_argument('old_name', help='The old name of the vtable symbol') + parser.add_argument('new_name', help='The new name of the vtable symbol') + parser.add_argument('--dry', action='store_true', help='Print the changes without writing to files') + args = parser.parse_args() + + old_name: str = args.old_name + new_name: str = args.new_name + dry_run: bool = args.dry + + file_write_buffer: list[tuple[Path, list[str]]] = [] + manual_changes: list[str] = [] + + current_path = Path(__file__).parent + root_path = current_path.parent + base_config_path = root_path / "config" + for config_path in base_config_path.iterdir(): + if config_path.is_file(): + continue + + old_address = None + new_address = None + dest_module = None + for symbol_file in config_path.glob("**/symbols.txt"): + with symbol_file.open("r") as f: + lines = f.readlines() + + for row, line in enumerate(lines): + if not line.startswith(old_name): + continue + print(f"Updating symbol {old_name} in {symbol_file}:{row + 1}") + address = get_attr_value(line, "addr") + if address is None: + print(f"Error: Could not find symbol address at {symbol_file}:{row + 1}") + exit(1) + address = int(address, 16) + old_address = address + new_address = address - 8 + line = line.replace(old_name, new_name, 1) + line = set_attr_value(line, "addr", f"0x{new_address:08x}") + print(f"-> {line}") + lines[row] = line + + if old_address is None or new_address is None: + # Try next symbols.txt file + continue + + file_name = str(symbol_file.relative_to(config_path)) + if file_name.endswith("dtcm/symbols.txt"): + dest_module = ("dtcm", 0) + elif file_name.endswith("itcm/symbols.txt"): + dest_module = ("itcm", 0) + elif file_name.endswith("arm9/symbols.txt"): + dest_module = ("main", 0) + else: + overlay_id = re.search(r"ov(\d+)/symbols.txt", file_name) + if overlay_id is None: + print(f"Error: Could not determine module for {symbol_file}") + exit(1) + dest_module = ("overlay", int(overlay_id.group(1))) + + file_write_buffer.append((symbol_file, lines)) + break + + if old_address is None or new_address is None or dest_module is None: + print(f"Error: Could not find symbol {old_name} in any symbols.txt file in {config_path}") + exit(1) + + for relocs_file in config_path.glob("**/relocs.txt"): + with relocs_file.open("r") as f: + lines = f.readlines() + + any_change = False + for row, line in enumerate(lines): + to_addr = get_attr_value(line, "to") + if to_addr is None: + continue + to_addr = int(to_addr, 16) + if to_addr != old_address: + continue + reloc_module = get_attr_value(line, "module") + if reloc_module is None: + continue + if dest_module[0] == "overlay" and reloc_module.startswith("overlays"): + print(f"Warning: Found ambiguous relocation for {old_name} in {relocs_file}, it will require manual review.") + manual_changes.append(f"{relocs_file}:{row + 1}") + if not reloc_module.startswith(dest_module[0]): + continue + + print(f"Updating relocation for {old_name} in {relocs_file}:{row + 1}") + line = set_attr_value(line, "to", f"0x{new_address:08x}") + line = set_attr_value(line, "add", "0x8") + print(f"-> {line}") + lines[row] = line + any_change = True + + if any_change: + file_write_buffer.append((relocs_file, lines)) + + if not dry_run: + for symbol_file, lines in file_write_buffer: + with symbol_file.open("w") as f: + f.writelines(lines) + print(f"Changes written to {len(file_write_buffer)} files:") + else: + print(f"Dry run complete. {len(file_write_buffer)} files would be updated:") + for symbol_file, _ in file_write_buffer: + print(f"- {symbol_file}") + +def get_attr_value(line: str, attr: str) -> str | None: + match = re.search(rf"{attr}:(\S+)", line) + if match is None: + return None + return match.group(1) + +def set_attr_value(line: str, attr: str, value: str) -> str: + pattern = rf"{attr}:\S+" + if not re.search(pattern, line): + line = line.strip() + f" {attr}:{value}\n" + return line + return re.sub(rf"{attr}:\S+", f"{attr}:{value}", line, count=1) + +if __name__ == "__main__": + main() |
