This repository was archived by the owner on Dec 26, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdecomp_getter.py
More file actions
executable file
·311 lines (265 loc) · 10.2 KB
/
decomp_getter.py
File metadata and controls
executable file
·311 lines (265 loc) · 10.2 KB
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
#!/bin/env python3
# SPDX-License-Identifier: CC0-1.0 OR Unlicense
# The map parsing is based on this asm-differ fork
# https://github.com/Dragorn421/asm-differ/blob/bc4189857a2218f320cd81d88ba508915c9201c2/diff.py#L969
# asm-differ is also in the public domain (unlicense)
import argparse
from pathlib import Path
import shutil
import os
import re
import json
from typing import (
Tuple,
Dict,
Iterable,
Generator,
)
def parse_map_file(
mapfile_lines: Iterable[str],
) -> Generator[Tuple[str, str, int, int, str], None, None]:
symbol_pattern = re.compile(r"[a-zA-Z_][0-9a-zA-Z_]*")
cur_section = None
cur_objfile = None
ram_to_rom = None
for line in mapfile_lines:
tokens = line.split()
if not tokens:
continue
# hardcode some things that mess up the map parsing.
# this map parsing doesn't need to be robust anyway
if (
tokens == ["Memory", "Configuration"]
or tokens[0] == "LOAD"
or tokens == ["OUTPUT(zelda_ocarina_mq_dbg.elf", "elf32-tradbigmips)"]
or tokens == ["*(.debug_info", ".gnu.linkonce.wi.*)"]
):
continue
if tokens[0] in {".text", ".data", ".rodata", ".bss"}:
cur_objfile = tokens[3]
cur_section = tokens[0]
elif line.startswith(" ."):
cur_section = None
if "load address" in line:
if len(tokens) == 6:
ram_str = tokens[1]
rom_str = tokens[5]
elif len(tokens) == 5:
# long segment names are put in the previous line, shifting tokens by 1
ram_str = tokens[0]
rom_str = tokens[4]
else:
raise Exception(
f"Unexpected amount of tokens {len(tokens)} {tokens} in line {line}"
)
ram = int(ram_str, 0)
rom = int(rom_str, 0)
ram_to_rom = rom - ram
# "load address" comes before sections
cur_objfile = None
cur_section = None
if len(tokens) == 2:
try:
offset = int(tokens[0], 0)
except ValueError:
raise Exception(f"Could not parse {tokens}")
if ram_to_rom is None:
ram = None
rom = offset
else:
ram = offset
rom = ram + ram_to_rom
symbol_name = tokens[1]
if symbol_pattern.fullmatch(symbol_name):
yield (
cur_objfile,
cur_section,
ram,
rom,
symbol_name,
)
else:
if not symbol_name.startswith("0x"):
raise Exception(f"Could not parse {tokens}")
def read_and_organize_symbols(mapfile_lines):
symbols: Dict[str, Dict[str, Dict[str, Tuple[int, int]]]] = dict()
for objfile, section, ram, rom, symbol in parse_map_file(mapfile_lines):
if objfile not in symbols:
symbols[objfile] = dict()
if section not in symbols[objfile]:
symbols[objfile][section] = dict()
if symbol in symbols[objfile][section]:
raise Exception("Duplicate symbol", symbol)
symbols[objfile][section][symbol] = (ram, rom)
# print(objfile, section, ram, rom, symbol)
if ram is None:
if symbol not in {"entrypoint"}:
raise Exception(
"the only expected symbol without a load address is entrypoint, encountered another one",
symbol,
)
else:
pass
return symbols
def update_z64hdr(
oot_decomp_repo_path: Path,
output_path_syms: Path,
output_path_includes: Path,
):
"""
`oot_decomp_repo_path` should be a `Path` to the oot decomp repo
for example `Path("/home/dragorn421/Documents/oot/")`
Writes syms_*.ld files under `output_path_syms`,
and copies undefined_syms.txt there,
and dumps the parsed symbols from the map to syms.json
Copies .h files from decomp's assets, include and src into `output_path_includes`
"""
for empty_dir_path in (output_path_syms, output_path_includes):
if empty_dir_path.exists():
while (
input(
f"Delete directory {empty_dir_path.absolute()} ? 'yes' to confirm: "
)
!= "yes"
):
pass
shutil.rmtree(empty_dir_path)
empty_dir_path.mkdir(parents=True, exist_ok=True)
# read map
with (oot_decomp_repo_path / "build" / "z64.map").open() as f:
mapfile_lines = f.readlines()
new_symbols = read_and_organize_symbols(mapfile_lines)
syms_dump = dict()
# write new linker script
# one out_lines_* per syms_*.ld file
out_lines_src = []
out_lines_assets_scenes = []
out_lines_assets_objects = []
out_lines_assets_others = []
out_lines_others = []
for objfile, objfile_symbols in new_symbols.items():
# decide to which syms_*.ld file append this .o file's symbols
out_lines = None
skip = False
if objfile.startswith("build/src/overlays/"):
# we don't need the symbols from overlays
# and because they get relocated using them usually makes little sense
skip = True
elif objfile.startswith("build/src/elf_message/"):
# this is data which symbols aren't used directly
skip = True
elif (
objfile == "build/asm/entry.o"
# decomp 4775fd4a7ea8077f603900def093124f8aa7cec6 : asm/entry.s -> src/makerom/entry.s
or objfile == "build/src/makerom/entry.o"
):
# skip `entrypoint` symbol
skip = True
elif objfile.startswith("build/src/"):
out_lines = out_lines_src
elif objfile.startswith("build/assets/scenes/"):
out_lines = out_lines_assets_scenes
elif objfile.startswith("build/assets/objects/"):
out_lines = out_lines_assets_objects
elif objfile.startswith("build/assets/"):
out_lines = out_lines_assets_others
else:
out_lines = out_lines_others
syms_dump[objfile] = {
section: {
symbol: {
"ram": f"0x{ram:08X}" if ram is not None else None,
"rom": (
f"0x{rom:08X}"
if rom is not None and section != ".bss"
else None
),
"used": not skip,
}
for symbol, (ram, rom) in section_symbols.items()
}
for section, section_symbols in objfile_symbols.items()
}
if skip:
continue
# append symbols
out_lines.append(f"/* {objfile} */")
for section in (".text", ".data", ".rodata", ".bss"):
section_symbols = objfile_symbols.get(section)
if section_symbols:
out_lines.append(f" /* {section} */")
symbol_pad = max(len(symbol) for symbol in section_symbols.keys())
for symbol, (ram, rom) in section_symbols.items():
try:
line = f" {symbol:<{symbol_pad}} = 0x{ram:08X};"
if section != ".bss":
line += f" /* ROM: 0x{rom:08X} */"
except:
print(
"Couldn't format line",
repr(symbol),
repr(ram),
repr(rom),
repr(symbol_pad),
repr(section),
repr(objfile),
)
raise
out_lines.append(line)
out_lines.append("")
with (output_path_syms / "syms.json").open("w") as f:
json.dump(syms_dump, f, indent=1)
# write syms_*.ld files
for out_file_name, out_lines in (
("src", out_lines_src),
("assets_scenes", out_lines_assets_scenes),
("assets_objects", out_lines_assets_objects),
("assets_others", out_lines_assets_others),
("others", out_lines_others),
):
with (output_path_syms / f"syms_{out_file_name}.ld").open("w") as f:
f.writelines(f"{line}\n" for line in out_lines)
with (oot_decomp_repo_path / "undefined_syms.txt").open() as f:
undefined_syms = f.read()
# convert single-line comments like `// abc` to `/* abc */`
# (I think linker scripts can only have the multiline style comments)
undefined_syms = re.sub(r"//([^\n]*)\n", "/*\\1 */\n", undefined_syms)
with (output_path_syms / "undefined_syms.txt").open("w") as f:
f.write(undefined_syms)
# copy headers
def copytree_ignore_onlycopyheaders(dir, entries: Iterable[str]):
ignored_entries = []
dirpath = Path(dir)
for entry in entries:
path = dirpath / entry
if not (path.is_dir() or entry.endswith(".h")):
ignored_entries.append(entry)
return ignored_entries
for folder in ("assets", "include", "src"):
shutil.copytree(
oot_decomp_repo_path / folder,
output_path_includes / folder,
ignore=copytree_ignore_onlycopyheaders,
)
cont = True
while cont:
empty_dirs = []
for dirpath, dirnames, filenames in os.walk(output_path_includes / folder):
if len(dirnames) == 0 and len(filenames) == 0:
empty_dirs.append(dirpath)
cont = len(empty_dirs) != 0
for empty_dir in empty_dirs:
os.rmdir(empty_dir)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("oot_decomp_repo_path")
parser.add_argument("output_path_syms")
parser.add_argument("output_path_includes")
args = parser.parse_args()
update_z64hdr(
oot_decomp_repo_path=Path(args.oot_decomp_repo_path),
output_path_syms=Path(args.output_path_syms),
output_path_includes=Path(args.output_path_includes),
)
if __name__ == "__main__":
main()