summaryrefslogtreecommitdiff
path: root/tools/vtable_sym.py
blob: 826cd6106be6f683027d63b62fb96c11b410dd22 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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()