summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
authorJonathan Wase <jonathan@familjenwase.se>2021-12-02 23:38:37 +0100
committerGitHub <noreply@github.com>2021-12-02 23:38:37 +0100
commitbc428f7f65b97cc9035aed1dc1b71c54ff2e6c3d (patch)
treea9ee5b38a79336db38dd126bec4b538a9a541bbd /tools
parent5f187a0776487afa64cb0993b1e96bb5ee115efd (diff)
Clean up and improvements to tools (#163)
* moved elf2dol * removed postprocess.py * removed vtables.py * find_unused_asm.py * removed section2cpp.py * removed splitter/* * fixed symbol names due to iconv file rename * fixed problem building RELs caused by #160 * improved performance of a few python tools * added new tool for finding conflict when not OK * added ./tp setup * don't install dol2asm dependecies with requirements.txt * format and check for imports * remove unused tools/difftools.py * fixed ignore to include elf2dol * fix compiler patcher * ok-check now creates the patched compiler at mwcceppc_patched.exe * Add new command to copy the build folder to the expected folder * 'make clean' will now only clean main.dol stuff. (added clean_rels and clean_all) * './tp pull-request' and './tp check' now doesn't include RELs by default. Use '--rels' to include them in the process. * './tp remove-unused-asm --check' added, exitcode 0==no files, 1==exists files Co-authored-by: Julgodis <>
Diffstat (limited to 'tools')
-rw-r--r--tools/.gitignore3
-rw-r--r--tools/Makefile8
-rw-r--r--tools/conflict.py438
-rw-r--r--tools/difftools.py294
-rw-r--r--tools/dol2asm.py134
-rw-r--r--tools/dol2asm_settings.py1638
-rw-r--r--tools/elf2dol/Makefile7
-rw-r--r--tools/elf2dol/elf2dol.c (renamed from tools/elf2dol.c)0
-rw-r--r--tools/extract_game_assets.py105
-rw-r--r--tools/find_unused_asm.py32
-rw-r--r--tools/lcf.py134
-rw-r--r--tools/libdol2asm/__init__.py8
-rw-r--r--tools/libdol2asm/requirements.txt9
-rw-r--r--tools/libelf/object.py182
-rw-r--r--tools/makerel.py196
-rw-r--r--tools/postprocess.py319
-rw-r--r--tools/rel.py124
-rw-r--r--tools/requirements.txt12
-rw-r--r--tools/section2cpp.py612
-rw-r--r--tools/splitter/asm_parser.py159
-rw-r--r--tools/splitter/demangle.py311
-rw-r--r--tools/splitter/split.py338
-rw-r--r--tools/splitter/util.py17
-rw-r--r--tools/tp.py1010
-rw-r--r--tools/vtables.py33
25 files changed, 3420 insertions, 2703 deletions
diff --git a/tools/.gitignore b/tools/.gitignore
index 702752f9fc..9f7177eba7 100644
--- a/tools/.gitignore
+++ b/tools/.gitignore
@@ -1,4 +1,3 @@
# Build artifacts
*.exe
-elf2dol
-vtable.lcf \ No newline at end of file
+__pycache__
diff --git a/tools/Makefile b/tools/Makefile
deleted file mode 100644
index b0253fb8ba..0000000000
--- a/tools/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-CC := cc
-CFLAGS := -O3 -Wall -s
-
-elf2dol: elf2dol.c
- $(CC) $(CFLAGS) -o $@ $^
-
-clean:
- $(RM) elf2dol
diff --git a/tools/conflict.py b/tools/conflict.py
new file mode 100644
index 0000000000..fe20e6c6da
--- /dev/null
+++ b/tools/conflict.py
@@ -0,0 +1,438 @@
+"""
+
+conflict.py - Finds conflicts between in main.dol that prevents it from matching.
+
+"""
+
+import sys
+import logging
+
+from pathlib import Path
+from collections import defaultdict
+
+try:
+ import click
+
+ from rich.logging import RichHandler
+ from rich.console import Console
+except ImportError as e:
+ MISSING_PREREQUISITES = (
+ f"Missing prerequisite python module {e}.\n"
+ f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites."
+ )
+
+ print(MISSING_PREREQUISITES, file=sys.stderr)
+ sys.exit(1)
+
+
+class PathPath(click.Path):
+ def convert(self, value, param, ctx):
+ return Path(super().convert(value, param, ctx))
+
+
+VERSION = "1.0"
+CONSOLE = Console()
+
+logging.basicConfig(
+ level="NOTSET",
+ format="%(message)s",
+ datefmt="[%X]",
+ handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)],
+)
+
+LOG = logging.getLogger("rich")
+LOG.setLevel(logging.INFO)
+
+
+@click.group()
+@click.version_option(VERSION)
+def conflict():
+ """Finds conflicts between in main.dol that prevents it from matching."""
+ pass
+
+
+class ConflictException(Exception):
+ pass
+
+
+def try_hex(value, padding):
+ if value == None:
+ return value
+
+ if not isinstance(value, int):
+ return value
+
+ return "0x{0:0{1}X}".format(value, padding)
+
+
+def normalize_name(name):
+ if name == None:
+ return None
+
+ # literals will have different indices, thus we cannot rely on their name
+ if name.startswith("@") or name.startswith("lit_"):
+ return None
+
+ return name
+
+
+def is_literal(name):
+ return name.startswith("@") or name.startswith("lit_")
+
+
+def name_match(A, B, addr):
+ if A == B:
+ return True
+ elif is_literal(A) and is_literal(B):
+ return True
+ elif A == B.replace("_o_iconv_cpp", "_cpp"): # TODO: remove, not needed any more
+ return True
+ elif A == f"func_{addr:08X}":
+ return True
+ elif A == f"data_{addr:08X}":
+ return True
+ elif B == f"func_{addr:08X}":
+ return True
+ elif B == f"data_{addr:08X}":
+ return True
+
+ return False
+
+
+#
+# All
+#
+@conflict.command(name="all")
+@click.option(
+ "--build_path",
+ "build_path",
+ required=False,
+ type=PathPath(file_okay=False, dir_okay=True),
+ default="build/dolzel2/",
+)
+@click.option(
+ "--expected_path",
+ "expected_path",
+ required=False,
+ type=PathPath(file_okay=False, dir_okay=True),
+ default="expected/build/dolzel2/",
+)
+def conflict_all(build_path, expected_path):
+ """Run all conflict checks."""
+
+ try:
+ sections(build_path, expected_path)
+ except ConflictException as exception:
+ LOG.error(exception)
+
+ try:
+ symbols(build_path, expected_path)
+ except ConflictException as exception:
+ LOG.error(exception)
+
+ CONSOLE.print("no conflicts were found 😊")
+
+
+#
+# Sections
+#
+@conflict.command(name="sections")
+@click.option(
+ "--build_path",
+ "build_path",
+ required=False,
+ type=PathPath(file_okay=False, dir_okay=True),
+ default="build/dolzel2/",
+)
+@click.option(
+ "--expected_path",
+ "expected_path",
+ required=False,
+ type=PathPath(file_okay=False, dir_okay=True),
+ default="expected/build/dolzel2/",
+)
+def conflict_sections(build_path, expected_path):
+ """Check if there are problems with the sections in the build compared with the expected build."""
+
+ try:
+ sections(build_path, expected_path)
+ except ConflictException as exception:
+ LOG.error(exception)
+
+
+def sections(build_path, expected_path):
+ import libelf
+ import libdol
+
+ belf_file = build_path.joinpath("main.elf")
+ eelf_file = expected_path.joinpath("main.elf")
+
+ # load elf
+ build = libelf.load_object_from_path(
+ belf_file, skip_symbols=True, skip_relocations=True
+ )
+ expected = libelf.load_object_from_path(
+ eelf_file, skip_symbols=True, skip_relocations=True
+ )
+
+ SECTION_NAMES = [y for x, y in libdol.NAMES_FOR_INDEX.items()]
+ bsection_names = [k for k in build.sections if k in SECTION_NAMES]
+ esection_names = [k for k in expected.sections if k in SECTION_NAMES]
+
+ if len(bsection_names) != len(esection_names):
+ raise ConflictException(
+ f"number of elf sections does not match (expected: {len(esection_names)}, got: {len(bsection_names)})"
+ )
+
+ for bsection_name, esection_name in zip(bsection_names, esection_names):
+ if bsection_name != esection_name:
+ raise ConflictException(
+ f"section names does not match (expected: '{esection_name}', got: '{bsection_name}')"
+ )
+
+ bsection = build.sections[bsection_name]
+ esection = expected.sections[esection_name]
+ if type(bsection) != type(esection):
+ raise ConflictException(
+ f"'{bsection_name}' section kinds does not match (expected: '{type(esection)}', got: '{type(bsection)}')"
+ )
+
+ if bsection.addr != esection.addr:
+ raise ConflictException(
+ f"'{bsection_name}' section addresses does not match (expected: {try_hex(esection.addr,8)}, got: {try_hex(bsection.addr,8)})"
+ )
+
+ if bsection.size != esection.size:
+ info = []
+ info.append(
+ f"'{bsection_name}' section sizes does not match (expected: {try_hex(esection.size,6)}, got: {try_hex(bsection.size,6)})"
+ )
+
+ if bsection.header.sh_addr != 0:
+ info.append(f"build section:")
+ info.append(f" begin: 0x{bsection.header.sh_addr:08X}")
+ info.append(
+ f" end: 0x{bsection.header.sh_addr + bsection.size:08X}"
+ )
+
+ if esection.header.sh_addr != 0:
+ info.append(f"expected section:")
+ info.append(f" begin: 0x{esection.header.sh_addr:08X}")
+ info.append(
+ f" end: 0x{esection.header.sh_addr + esection.size:08X}"
+ )
+
+ raise ConflictException("\n".join(info))
+
+ for bsection_name, esection_name in zip(bsection_names, esection_names):
+ bsection = build.sections[bsection_name]
+ esection = expected.sections[esection_name]
+
+ if bsection.data != esection.data:
+ position = -1
+ for index, tup in enumerate(zip(esection.data, bsection.data)):
+ if tup[0] != tup[1]:
+ position = index
+ break
+
+ info = []
+ if position >= 0:
+ info.append(f"'{bsection_name}' sections data does not match")
+ info.append(
+ f"first difference is at position {position} (0x{position:04X}) (expected: 0x{tup[0]:02X}, got: 0x{tup[1]:02X})"
+ )
+
+ if bsection.header.sh_addr != 0:
+ build_location = bsection.header.sh_addr + position
+ info.append(f"build location:")
+ info.append(f" addr: 0x{build_location:08X}")
+
+ if esection.header.sh_addr != 0:
+ expected_location = esection.header.sh_addr + position
+ info.append(f"expected location:")
+ info.append(f" addr: 0x{expected_location:08X}")
+ else:
+ info.append(f"could not determine the byte difference")
+
+ raise ConflictException("\n".join(info))
+
+ # TODO: more checks?
+
+
+#
+# symbols
+#
+@conflict.command(name="symbols")
+@click.option(
+ "--build_path",
+ "build_path",
+ required=False,
+ type=PathPath(file_okay=False, dir_okay=True),
+ default="build/dolzel2/",
+)
+@click.option(
+ "--expected_path",
+ "expected_path",
+ required=False,
+ type=PathPath(file_okay=False, dir_okay=True),
+ default="expected/build/dolzel2/",
+)
+def conflict_symbols(build_path, expected_path):
+ """Check if there are problems with the symbols in the build compared with the expected build."""
+
+ try:
+ symbols(build_path, expected_path)
+ except ConflictException as exception:
+ LOG.error(exception)
+
+
+def symbols(build_path, expected_path):
+ import libelf
+ import libdol
+
+ belf_file = build_path.joinpath("main.elf")
+ eelf_file = expected_path.joinpath("main.elf")
+
+ # load elf
+ build = libelf.load_object_from_path(
+ belf_file, skip_symbols=False, skip_relocations=True
+ )
+ expected = libelf.load_object_from_path(
+ eelf_file, skip_symbols=False, skip_relocations=True
+ )
+
+ # assign section address
+ for _, section in build.sections.items():
+ if section.header.sh_addr == 0:
+ continue
+ section.addr = section.header.sh_addr
+
+ for _, section in expected.sections.items():
+ if section.header.sh_addr == 0:
+ continue
+ section.addr = section.header.sh_addr
+
+ # build dictionary of symbol
+ def strip_filter(symbol):
+ if isinstance(symbol, libelf.AbsoluteSymbol):
+ # we're not checking for conflict between absolute symbols,
+ # they are generated by the lcf.py script and are only temporary.
+ return False
+
+ if symbol.name == None:
+ # we only care about symbols with names
+ return False
+
+ return True
+
+ build_stripped_symbols = [x for x in build.symbols if strip_filter(x)]
+ expected_stripped_symbols = [x for x in expected.symbols if strip_filter(x)]
+
+ build_name2symbols = defaultdict(list)
+ for symbol in build_stripped_symbols:
+ build_name2symbols[symbol.name].append(symbol)
+
+ expected_name2symbols = defaultdict(list)
+ for symbol in expected_stripped_symbols:
+ expected_name2symbols[symbol.name].append(symbol)
+
+ build_addr2sym = {k.offset: k for k in build_stripped_symbols}
+ expected_addr2sym = {k.offset: k for k in expected_stripped_symbols}
+
+ build_symbol_address_list = list(build_addr2sym.keys())
+ build_symbol_address_list.sort()
+
+ check_address_set = set()
+ for i, symbol_addr in enumerate(build_symbol_address_list):
+ symbol = build_addr2sym[symbol_addr]
+
+ if not symbol.offset in expected_addr2sym:
+ info = []
+ info.append(f"symbol not found")
+ info.append(f" section: {symbol.getSection().name}")
+ info.append(f" addr: 0x{symbol.offset:08X}")
+ info.append(f" size: 0x{symbol.size:05X}")
+ info.append(f" name: {symbol.name}")
+ raise ConflictException("\n".join(info))
+
+ expected_symbol = expected_addr2sym[symbol.offset]
+ if symbol.size != expected_symbol.size:
+ # because of dol2asm all data elements, before they are decompiled, will include
+ # padding. when decompiling the padding may get removed, and thus this tool will
+ # report a false-positive size difference. to fix this, find the offset to the next
+ # symbol (in the same section) and make sure it is located at the expected location.
+ next_symbol = None
+ current_section = symbol.getSection()
+ i += 1 # skip current symbol
+ if i < len(build_symbol_address_list):
+ i_addr = build_symbol_address_list[i]
+ i_symbol = build_addr2sym[i_addr]
+ if i_symbol.getSection() == current_section:
+ next_symbol = i_symbol
+
+ false_positive = False
+ if next_symbol:
+ difference = next_symbol.offset - symbol.offset
+ if difference == expected_symbol.size:
+ false_positive = True
+
+ if not false_positive:
+ info = []
+ info.append(
+ f"size difference (expected: 0x{expected_symbol.size:05X}, got: 0x{symbol.size:05X})"
+ )
+ info.append(f"symbol:")
+ info.append(f" section: {symbol.getSection().name}")
+ info.append(f" addr: 0x{symbol.offset:08X}")
+ info.append(f" size: 0x{symbol.size:05X}")
+ info.append(f" name: {symbol.name}")
+ info.append(f"expected symbol:")
+ info.append(f" section: {expected_symbol.getSection().name}")
+ info.append(f" addr: 0x{expected_symbol.offset:08X}")
+ info.append(f" size: 0x{expected_symbol.size:05X}")
+ info.append(f" name: {expected_symbol.name}")
+ raise ConflictException("\n".join(info))
+
+ if not name_match(symbol.name, expected_symbol.name, symbol.offset):
+ info = []
+ info.append(
+ f"name difference (expected: '{expected_symbol.name}', got: '{symbol.name}')"
+ )
+ info.append(f"symbol:")
+ info.append(f" section: {symbol.getSection().name}")
+ info.append(f" addr: 0x{symbol.offset:08X}")
+ info.append(f" size: 0x{symbol.size:05X}")
+ info.append(f" name: {symbol.name}")
+ info.append(f"expected symbol:")
+ info.append(f" section: {expected_symbol.getSection().name}")
+ info.append(f" addr: 0x{expected_symbol.offset:08X}")
+ info.append(f" size: 0x{expected_symbol.size:05X}")
+ info.append(f" name: {expected_symbol.name}")
+ raise ConflictException("\n".join(info))
+
+ check_address_set.add(symbol.offset)
+
+ expected_symbol_address_list = list(expected_addr2sym.keys())
+ expected_symbol_address_list.sort()
+
+ for symbol_addr in expected_symbol_address_list:
+ if symbol_addr in check_address_set:
+ continue
+
+ expected_symbol = build_addr2sym[symbol_addr]
+ info = []
+ info.append(f"missing symbol")
+ info.append(f"expected symbol:")
+ info.append(f" section: {expected_symbol.getSection().name}")
+ info.append(f" addr: 0x{expected_symbol.offset:08X}")
+ info.append(f" size: 0x{expected_symbol.size:05X}")
+ info.append(f" name: {expected_symbol.name}")
+ raise ConflictException("\n".join(info))
+
+
+#
+#
+#
+
+if __name__ == "__main__":
+ conflict()
diff --git a/tools/difftools.py b/tools/difftools.py
deleted file mode 100644
index 24e40a4325..0000000000
--- a/tools/difftools.py
+++ /dev/null
@@ -1,294 +0,0 @@
-
-"""
-
-difftools.py - Tools for finding differences between binaries
-
-TODO: NOT FINISHED!
-
-"""
-
-VERSION = "1.0"
-
-import os
-import sys
-import click
-from pathlib import Path
-from collections import defaultdict
-
-class PathPath(click.Path):
- def convert(self, value, param, ctx):
- return Path(super().convert(value, param, ctx))
-
-def fail(name):
- sys.exit(1)
-
-import libelf
-
-@click.group()
-@click.version_option(VERSION)
-def difftools():
- """ Tools for finding differences. """
- pass
-
-@difftools.command(name="addr")
-@click.option('--truth', '-t', default="SYMDEF", type=click.Choice(['SYMDEF', 'EXPECTED', 'S', 'E'], case_sensitive=False))
-@click.option('--build_path', 'build_path', required=False, type=PathPath(file_okay=False, dir_okay=True), default="build/dolzel2/")
-@click.option('--expected_path', 'expected_path', required=False, type=PathPath(file_okay=False, dir_okay=True), default="expected/build/dolzel2/")
-def find_bad_symbol_addr(truth, build_path, expected_path):
- """ Find symbols which have the incorrect address. """
-
- build_symbols = []
- build_elf = build_path.joinpath("main.elf")
- if not build_elf.exists():
- fail(f"file not found: elf file '{build_elf}'")
- build_symbols.extend(symbols_from_elf(build_elf))
-
- expected_symbols = []
- if truth == "EXPECTED" or truth == "E":
- if not expected_path:
- fail(f"when 'truth={truth}' the input argument 'expected_path' must be provided")
-
- expected_elf = expected_path.joinpath("main.elf")
- if not expected_elf.exists():
- fail(f"file not found: expected elf file '{expected_elf}'")
- expected_symbols.extend(symbols_from_elf(expected_elf))
- else:
- assert False
-
- # match symbols by names
- names = defaultdict(list)
- for symbol in expected_symbols:
- names[symbol.name].append(symbol)
-
- build_addr_map = dict()
- for symbol in build_symbols:
- build_addr_map[elf_symbol_addr(symbol)] = symbol
-
- # find matching symbols
- last_difference = 0
- build_symbols.sort(key =lambda x: elf_symbol_addr(x))
- for symbol in build_symbols:
- if not symbol.name in names:
- continue
-
- difference, closest_symbol = closest_match(symbol, names[symbol.name])
- if difference != 0:
- build_addr = elf_symbol_addr(symbol)
- closest_addr = elf_symbol_addr(closest_symbol)
-
- print("symbol with address difference found:")
- print(f"\tname: '{symbol.name}'")
- print(f"\tsection: '{symbol.section.name}'")
- print(f"\tpath: '{symbol.object_path}'")
- print("")
- print(f"\tcompiled addr: 0x{build_addr:08X}")
- print(f"\texpected addr: 0x{closest_addr:08X}")
- print("")
-
- previous_symbol, previous_addr = symbol_from_end(build_symbols, build_addr)
- expected_symbol = symbol_at_addr(expected_symbols, previous_addr)
- if previous_symbol and expected_symbol:
- print("this is the expected symbol before the problem symbol:")
- previous_start = elf_symbol_addr(previous_symbol)
- previous_end = previous_start + previous_symbol.size
- print(f"\t{previous_start:08X} {previous_end:08X} {previous_symbol.size:04X} {previous_symbol.name} (compiled)")
-
- expected_start = elf_symbol_addr(expected_symbol)
- expected_end = expected_start + expected_symbol.size
- print(f"\t{expected_start:08X} {expected_end:08X} {expected_symbol.size:04X} {expected_symbol.name} (expected)")
-
- if previous_symbol.size != expected_symbol.size:
- print("\t!!! the size of this symbol is incorrect !!!")
- sys.exit()
-
- if expected_end != previous_end:
- print("\t!!! the size of this symbol is incorrect !!!")
- sys.exit()
-
- inbetween_symbol = symbol_at_addr(expected_symbols, expected_end)
- if inbetween_symbol:
- print("found extra symbol in expected:")
- start = elf_symbol_addr(inbetween_symbol)
- end = start + inbetween_symbol.size
- print(f"\t{start:08X} {end:08X} {inbetween_symbol.size:04X} {inbetween_symbol.name}")
- print("\t!!! the compiled version is missing this symbol !!!")
-
- sys.exit()
-
- if symbol.size != closest_symbol.size:
- print("symbol with size difference found:")
- print(f"\tname: '{symbol.name}'")
- print(f"\tsection: '{symbol.section.name}'")
- print(f"\tpath: '{symbol.object_path}'")
- print("")
- print(f"\tcompiled size: 0x{symbol.size:04X}")
- print(f"\texpected size: 0x{closest_symbol.size:04X}")
- sys.exit()
-
- sys.exit()
-
- for symbol in expected_symbols:
- addr = elf_symbol_addr(symbol)
-
- if not addr in build_addr_map:
- print("compiled is missing symbol:")
- print(f"\tname: '{symbol.name}'")
- print(f"\tsection: '{symbol.section.name}'")
- print(f"\tpath: '{symbol.object_path}'")
- print(f"\taddr: 0x{addr:08X}")
- print(f"\tsize: 0x{size:04X}")
- sys.exit()
-
-@difftools.command(name="info")
-@click.option('--truth', '-t', default="SYMDEF", type=click.Choice(['SYMDEF', 'EXPECTED', 'S', 'E'], case_sensitive=False))
-@click.option('--build_path', 'build_path', required=False, type=PathPath(file_okay=False, dir_okay=True), default="build/dolzel2/")
-@click.option('--expected_path', 'expected_path', required=False, type=PathPath(file_okay=False, dir_okay=True), default="expected/build/dolzel2/")
-@click.argument('names', nargs=-1)
-def info_about_symbol(truth, build_path, expected_path, names):
- """ Display information about symbols (both in the build and the truth). """
-
- build_symbols = []
- build_elf = build_path.joinpath("main.elf")
- if not build_elf.exists():
- fail(f"file not found: elf file '{build_elf}'")
- build_symbols.extend(symbols_from_elf(build_elf))
-
- expected_symbols = []
- if truth == "EXPECTED" or truth == "E":
- if not expected_path:
- fail(f"when 'truth={truth}' the input argument 'expected_path' must be provided")
-
- expected_elf = expected_path.joinpath("main.elf")
- if not expected_elf.exists():
- fail(f"file not found: expected elf file '{expected_elf}'")
- expected_symbols.extend(symbols_from_elf(expected_elf))
- else:
- assert False
-
- for name in names:
- build_symbols = symbols_by_name(build_symbols, name)
- expected_symbols = symbols_by_name(expected_symbols, name)
-
- print(f"###### {name} ######")
- print("-- Build --")
- for s in build_symbols:
- print(f"\t{elf_symbol_addr(s):08X} {s.size:04X} {s.name}")
- print("-- Expected --")
- for s in expected_symbols:
- print(f"\t{elf_symbol_addr(s):08X} {s.size:04X} {s.name}")
-
-def symbols_by_name(symbols, name):
- return [ symbol for symbol in symbols if symbol.name == name ]
-
-def symbol_at_addr(symbols, addr):
- for symbol in symbols:
- start = elf_symbol_addr(symbol)
- if start == addr:
- return symbol
- return None
-
-def symbol_from_end(symbols, end_addr):
- for symbol in symbols:
- start = elf_symbol_addr(symbol)
- end = start + symbol.size
- if end >= end_addr:
- return symbol, start
- return None
-
-def closest_match(symbol, matches):
- difference = 0x1000000
- closest_symbol = None
- build_addr = elf_symbol_addr(symbol)
- for found_symbol in matches:
- expected_addr = elf_symbol_addr(found_symbol)
- diff = abs(build_addr - expected_addr)
- if diff < difference:
- closest_symbol = found_symbol
- difference = diff
- return difference, closest_symbol
-
-def elf_symbol_addr(symbol):
- addr = symbol.offset
- if symbol.section and symbol.section.addr:
- addr += symbol.section.addr
- return addr
-
-def symbols_from_object(obj):
- # TODO: possible to read what translation unit each symbol is in
- symbols = []
- for sym in obj.symbols:
- if sym.isSection():
- continue
- if sym.isFile():
- continue
- if not isinstance(sym, libelf.OffsetSymbol):
- continue
- setattr(sym, 'object_path', obj.path) # TODO: not the best...
- symbols.append(sym)
- return symbols
-
-def symbols_from_elf(path):
- with open(path, 'rb') as file:
- obj = libelf.load_object_from_file(path, path.name, file)
- return symbols_from_object(obj)
- return []
-
-@difftools.command(name="section")
-@click.option('--truth', '-t', default="SYMDEF", type=click.Choice(['SYMDEF', 'EXPECTED', 'S', 'E'], case_sensitive=False))
-@click.option('--build_path', 'build_path', required=False, type=PathPath(file_okay=False, dir_okay=True), default="build/dolzel2/")
-@click.option('--expected_path', 'expected_path', required=False, type=PathPath(file_okay=False, dir_okay=True), default="expected/build/dolzel2/")
-@click.argument('section', nargs=1)
-def section_diff(truth, build_path, expected_path, section):
-
- build_symbols = []
- build_elf = build_path.joinpath("main.elf")
- if not build_elf.exists():
- fail(f"file not found: elf file '{build_elf}'")
- build_symbols.extend(symbols_from_elf(build_elf))
-
- expected_symbols = []
- if truth == "EXPECTED" or truth == "E":
- if not expected_path:
- fail(f"when 'truth={truth}' the input argument 'expected_path' must be provided")
-
- expected_elf = expected_path.joinpath("main.elf")
- if not expected_elf.exists():
- fail(f"file not found: expected elf file '{expected_elf}'")
- expected_symbols.extend(symbols_from_elf(expected_elf))
- else:
- assert False
-
- build_symbols.sort(key=lambda x:elf_symbol_addr(x))
- expected_symbols.sort(key=lambda x:elf_symbol_addr(x))
-
- build_dict = dict()
- expected_dict = dict()
-
- for symbol in build_symbols:
- if symbol.section.name != section:
- continue
- build_dict[elf_symbol_addr(symbol)] = symbol
- for symbol in expected_symbols:
- if symbol.section.name != section:
- continue
- expected_dict[elf_symbol_addr(symbol)] = symbol
-
- keys = set([*build_dict.keys(), *expected_dict.keys()])
- keys_list = list(keys)
- keys_list.sort()
-
- for key in keys_list:
- in_build = key in build_dict
- in_expected = key in expected_dict
-
- if in_build and not in_expected:
- print(f"+ {key:08X} {build_dict[key].size:04X} {build_dict[key].section.name:<10} '{build_dict[key].name}'")
- elif not in_build and in_expected:
- print(f"- {key:08X} {expected_dict[key].size:04X} {expected_dict[key].section.name:<10} '{expected_dict[key].name}'")
- else:
- build_sym = build_dict[key]
- expected_sym = expected_dict[key]
- print(f"= {key:08X} {build_sym.size:04X}/{expected_sym.size:04X} {build_sym.section.name:<10} '{build_sym.name}' '{expected_sym.name}'")
-
-if __name__ == "__main__":
- difftools() \ No newline at end of file
diff --git a/tools/dol2asm.py b/tools/dol2asm.py
index 65b8b99deb..222d4b3234 100644
--- a/tools/dol2asm.py
+++ b/tools/dol2asm.py
@@ -5,33 +5,125 @@ dol2asm.py - Script for splitting .dol and .rel binaries into C++ and .s code.
This script only calls the underlaying libdol2asm code that does the heavy-lifting.
"""
-
-import click
-import libdol2asm
+import sys
from pathlib import Path
+try:
+ import click
+ import libdol2asm
+except ImportError as e:
+ MISSING_PREREQUISITES = (
+ f"Missing prerequisite python module {e}.\n"
+ f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites."
+ )
+
+ print(MISSING_PREREQUISITES, file=sys.stderr)
+ sys.exit(1)
+
@click.command()
@click.version_option(libdol2asm.VERSION)
-@click.option('--debug/--no-debug', help="enable/disable debug logging", default=False)
-@click.option('--game', 'game_path', help=f"Path to extracted game files. (same directory as 'main.dol' is in)", required=False, type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True), default="game/")
-@click.option('--lib-path', 'lib_path', help="Where to put generated library source files.", required=False, type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True), default="libs/")
-@click.option('--src-path', 'src_path', help="Where to put generated source files.", required=False, type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True), default="src/")
-@click.option('--asm-path', 'asm_path', help="Where to put generated asm files.", required=False, type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True), default="asm/")
-@click.option('--rel-path', 'rel_path', help="Where to put generated rel source files.", required=False, type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True), default="rel/")
-@click.option('--include-path', 'inc_path', help="Where to put generated include files.", required=False, type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True), default="include/")
-@click.option('--cpp/--no-cpp', 'cpp_gen', default=True)
-@click.option('--asm/--no-asm', 'asm_gen', default=True)
-@click.option('--makefile/--no-makefile', 'mk_gen', default=True)
-@click.option('--symbols/--no-symbols', 'sym_gen', default=True)
-@click.option('--rels/--no-rels', 'rel_gen', default=True)
-@click.option('--threads', '-j', 'process_count', default=8)
-@click.option('--select-module', '-g', 'select_modules', help="Select what modules to generate. Default is everything.", multiple=True)
-@click.option('--select-asm', '-n', 'select_asm', multiple=True)
-@click.option('--select-tu', '-t', 'select_tu', multiple=True)
-def main(debug, game_path, asm_path, lib_path, src_path, rel_path, inc_path, mk_gen, cpp_gen, asm_gen, sym_gen, rel_gen, process_count, select_modules, select_tu, select_asm):
- return libdol2asm.split(debug, game_path, lib_path, src_path, asm_path, rel_path, inc_path, mk_gen, cpp_gen, asm_gen, sym_gen, rel_gen, process_count, select_modules, select_tu, select_asm)
+@click.option("--debug/--no-debug", help="enable/disable debug logging", default=False)
+@click.option(
+ "--game",
+ "game_path",
+ help=f"Path to extracted game files. (same directory as 'main.dol' is in)",
+ required=False,
+ type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True),
+ default="game/",
+)
+@click.option(
+ "--lib-path",
+ "lib_path",
+ help="Where to put generated library source files.",
+ required=False,
+ type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True),
+ default="libs/",
+)
+@click.option(
+ "--src-path",
+ "src_path",
+ help="Where to put generated source files.",
+ required=False,
+ type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True),
+ default="src/",
+)
+@click.option(
+ "--asm-path",
+ "asm_path",
+ help="Where to put generated asm files.",
+ required=False,
+ type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True),
+ default="asm/",
+)
+@click.option(
+ "--rel-path",
+ "rel_path",
+ help="Where to put generated rel source files.",
+ required=False,
+ type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True),
+ default="rel/",
+)
+@click.option(
+ "--include-path",
+ "inc_path",
+ help="Where to put generated include files.",
+ required=False,
+ type=libdol2asm.util.PathPath(file_okay=False, dir_okay=True),
+ default="include/",
+)
+@click.option("--cpp/--no-cpp", "cpp_gen", default=True)
+@click.option("--asm/--no-asm", "asm_gen", default=True)
+@click.option("--makefile/--no-makefile", "mk_gen", default=True)
+@click.option("--symbols/--no-symbols", "sym_gen", default=True)
+@click.option("--rels/--no-rels", "rel_gen", default=True)
+@click.option("--threads", "-j", "process_count", default=8)
+@click.option(
+ "--select-module",
+ "-g",
+ "select_modules",
+ help="Select what modules to generate. Default is everything.",
+ multiple=True,
+)
+@click.option("--select-asm", "-n", "select_asm", multiple=True)
+@click.option("--select-tu", "-t", "select_tu", multiple=True)
+def main(
+ debug,
+ game_path,
+ asm_path,
+ lib_path,
+ src_path,
+ rel_path,
+ inc_path,
+ mk_gen,
+ cpp_gen,
+ asm_gen,
+ sym_gen,
+ rel_gen,
+ process_count,
+ select_modules,
+ select_tu,
+ select_asm,
+):
+ return libdol2asm.split(
+ debug,
+ game_path,
+ lib_path,
+ src_path,
+ asm_path,
+ rel_path,
+ inc_path,
+ mk_gen,
+ cpp_gen,
+ asm_gen,
+ sym_gen,
+ rel_gen,
+ process_count,
+ select_modules,
+ select_tu,
+ select_asm,
+ )
if __name__ == "__main__":
diff --git a/tools/dol2asm_settings.py b/tools/dol2asm_settings.py
new file mode 100644
index 0000000000..571d4f50ef
--- /dev/null
+++ b/tools/dol2asm_settings.py
@@ -0,0 +1,1638 @@
+"""
+
+Game information and settings.
+TODO: remove the duplicate information in libdol2asm
+
+"""
+
+GAME_NAME = "The Legend of Zelda: Twilight Princess (GCN USA)"
+ENTRY_POINT = 0x80003154 # TODO: get from dol?
+
+SHA1 = {
+ "main.dol": "4997D93B9692620C40E90374A0F1DBF0E4889395",
+ "d_a_obj_lv4floor.rel": "BB67106ABB1D791B0F2BE5ECA33ED438B34ED1D5",
+ "d_a_e_ws.rel": "37E5B7E59B5BEF10F5B0A71B20A12BC4D178FD9E",
+ "d_a_obj_scannon_ten.rel": "1B91EE7A761DC04ADB27F8D7FA813D3AFE227F04",
+ "d_a_obj_crvhahen.rel": "472A4DFA5572DE1B63956EFD9943F99CFD6373DC",
+ "d_a_obj_lv4hstarget.rel": "FAC83D41C43DD6BA50E0D49FF2F3C755D265580C",
+ "d_a_obj_kipot.rel": "E8D7840E492D18726B34E4FFB4ED5695F9E37DB1",
+ "d_a_e_ym.rel": "614E1C02C1D59EEF6CCC9C4BA7B8F1987129C0D3",
+ "d_a_obj_firewood2.rel": "E07069778C4347F5F342DE23D77B090B05710E38",
+ "d_a_b_bh.rel": "D526C76F033EB01D4461D320F6F54843FE463084",
+ "d_a_obj_lv4bridge.rel": "812243A91AF29C45CA797A10C9719B17FECC3793",
+ "d_a_npc_doc.rel": "06ACE0D2CB1E812F62E1657BD3F3CB0C29A1D53B",
+ "d_a_obj_suisya.rel": "C8D5B30FB124099C1930460E1815AB9C1CEE23AA",
+ "d_a_hozelda.rel": "DBDB321E2EAC89C16F2DCB59CFF5D42B2A69FD01",
+ "d_a_npc_tkc.rel": "AFDB3839D806F770DC2D25FA2D05F7FB7FD15911",
+ "d_a_obj_tatigi.rel": "4560DDF21E501B0B267CC30534ADC946FD828E33",
+ "d_a_npc_aru.rel": "A0F69D08146E68DC7FB9F33682E30D4297295B0B",
+ "d_a_e_po.rel": "8D19D7F122CE50DABA8551EFC956537DED345D7B",
+ "d_a_tag_watchge.rel": "D5965FDB0659F08860CEDFC9EB20E1243F008915",
+ "d_a_obj_volcbom.rel": "7B2AFAE01C4AA78193A5D6E422D13F308005086D",
+ "d_a_npc_kolinb.rel": "58530C4352D5BFF2EE3308B9AD8BF9DA4F80356E",
+ "d_a_e_dt.rel": "CE8C9336606E8AFF27EC4AADD95E23A1E1087075",
+ "d_a_e_bi.rel": "90E48404744F4CAD16246DD88875CEF939B4585E",
+ "d_a_e_tk.rel": "194B1F691088AC31545A43C5284FABCBDE23AE80",
+ "d_a_npc_knj.rel": "AEDEFB66C08539D45A1BACD0B96E082255D790EB",
+ "d_a_obj_gra2.rel": "FFC2A2198CD570B34D367D9652E84BF8B03C60ED",
+ "d_a_obj_laundry_rope.rel": "D66373CEC8D2D841B4235F8F3EA33918220AFA72",
+ "d_a_obj_oiltubo.rel": "DC45740FAFE80F3F78F7844D45570C80FB730DFB",
+ "d_a_e_ai.rel": "F0C4193FC1A3F81D38D64D50C6AE62402B2BF1FD",
+ "d_a_b_yo_ice.rel": "0076EE6744F39309F2D6EED029644597AE97A774",
+ "d_a_obj_mirror_sand.rel": "7C6550EBE284CB50B330A127B59260AC668670CE",
+ "d_a_obj_scannon.rel": "861575980F36CD8D629189EA79CCBABE2AA2675D",
+ "d_a_b_dre.rel": "72E1A241D91531AC2EE0D2623C560FE3262A5C06",
+ "d_a_obj_bardesk.rel": "A27E27D2B614DD514C1F60BD2E426AD373CBC687",
+ "d_a_obj_y_taihou.rel": "39DF1A7F35E5C79BF561FBD6EF9790D678CE7D47",
+ "d_a_ni.rel": "DB1737A9555DD21C69372849895706FA39767D75",
+ "d_a_npc_raca.rel": "831319B3299BB3222C6E46BE48986D2C4319F2ED",
+ "d_a_e_md.rel": "A21302F2F6C1ACC2D0EEC075CBD5EF9168E5FA2D",
+ "d_a_obj_hbombkoya.rel": "F3C7954649C32DB792FB2EE420FBBFC27BE6225A",
+ "d_a_npc_pachi_taro.rel": "32A18DB8D4F376E6927B4DCF8649BADC54A65B87",
+ "d_a_npc_rafrel.rel": "4C55DC0E05D97813657BAB578B2C6494AA4EC849",
+ "d_a_obj_keyhole.rel": "DC821A03F47DCBA72C8031E58C0034E2B24859F1",
+ "d_a_npc_seirei.rel": "AC7C43AA33642F26B9913DFDF302D1FDFE24CE40",
+ "d_a_obj_ki.rel": "BD610B5E1FAD869B157D39BFDEDA9E87F797FF95",
+ "d_a_obj_lv6swturn.rel": "1867CE9B871B31D447EE6907A3562259DA107E89",
+ "d_a_obj_twgate.rel": "FEF293222015662D9E4525CC2B8A9638E4B4A04F",
+ "d_a_obj_bsgate.rel": "0664AC9A85C4E5FEB0E5014F1CE83676246BBFCC",
+ "d_a_obj_zra_freeze.rel": "37341A5ACD5D74D48A92509E1C7ECEBBBF7A5F1A",
+ "d_a_obj_waterfall.rel": "F4C57254D652C8659C04D7ADC5BE8D032060048B",
+ "d_a_e_tk2.rel": "0E96A4B2B6ED88FDE15D035C757AC7BA3EF4B770",
+ "d_a_mg_fshop.rel": "AECB40F35B5D77F419DB0AD612F497A9D6FA92DB",
+ "d_a_movie_player.rel": "A931EBD7F29BE04745E175066F5CB31FAC5E3309",
+ "d_a_e_fk.rel": "33FC905BD7FEC76AE15E20834F311B0D3CD8595C",
+ "d_a_npc_doorboy.rel": "6171B8E17477736E9FFD1D875A04C0BDBE4C1D1C",
+ "d_a_obj_kita.rel": "0688E31A09D99F77B7A01945A47F7D2425C755B4",
+ "d_a_obj_nan.rel": "91D20D5A484AE07F02956464807CFE0BCF5CD287",
+ "d_a_obj_testcube.rel": "37990390B9E0790D389DFEE25EA96DF08C28D2F5",
+ "d_a_obj_batta.rel": "1FD4DE73CF6F314738AF3B4F24AFF00405D228C1",
+ "d_a_obj_udoor.rel": "C494055D634EC9A94883D195445197BD9E94AC6B",
+ "d_a_e_db_leaf.rel": "5B4C86BE083FD2838B9F2E162424CD71E2C86344",
+ "d_a_npc_zra.rel": "2F07CA8708B0A78F45406575D3A46247EC08D413",
+ "d_a_obj_damcps.rel": "80B924EAEC011A7614F961D9A0492A062429B6EC",
+ "d_a_e_ms.rel": "946BC1C1B195F7246BA974D5A0CEAFBADD5B4597",
+ "d_a_obj_tobyhouse.rel": "7B79C550AC74A2C587A4F99A193895C6300F536F",
+ "d_a_obj_enemy_create.rel": "91437F1C6E88D20AABA581166AAC2BFE49C62155",
+ "d_a_e_cr_egg.rel": "5EB2D3799283346BC3867818A9FCF6482965CDCF",
+ "d_a_obj_tvcdlst.rel": "913C21B860FE34D0423D140C4D7861ADC9EAD2EA",
+ "d_a_npc_chin.rel": "5A58FC4731B5EE0E56F5752B702603A85B8B81F4",
+ "d_a_ppolamp.rel": "28A407D07E870996909B526790A5ECA577C6EB18",
+ "d_a_obj_lv8optilift.rel": "5DCD03E045CC3FA09CB902040773C97A0B6204C8",
+ "d_a_tag_myna2.rel": "4906EADCBB44DC6570105172B5D9E109FF17B370",
+ "d_a_tag_lv6gate.rel": "11C2B2A9AC87E4918DBE2C5549D148B97DB0CCFB",
+ "d_a_obj_avalanche.rel": "F8866BDCF1ED0CC0E45680E1945CE2F9E18F36DC",
+ "d_a_obj_glowsphere.rel": "C9E5CD04324D0B2D72667707AE22ADEEA2ECD6C0",
+ "d_a_b_yo.rel": "407ABBA0D8F36087DDF8B35DBCB0E05F66A39780",
+ "d_a_obj_lv4candledemotag.rel": "97CC3C734AFAEE6F544AB22A625EAAFB42693323",
+ "d_a_obj_pillar.rel": "22CE01FF188752D93BEFA4C50782859347814852",
+ "d_a_npc_zant.rel": "B86CD8201781B6527A71E0851905DC812545D8C3",
+ "d_a_obj_fchain.rel": "C06F7411914FEAD2FB5FDE85BD0FBDB2B0B385FE",
+ "d_a_obj_turara.rel": "D458AA3CE8D4FCD19B4436CEA8E1048B84AA9F36",
+ "d_a_npc_zelro.rel": "CFAF808A0A35B7A46ACD6B8C013ACF6BA72146AE",
+ "d_a_obj_tgake.rel": "C6CB86BE803A905CE310F9DBA6A1428F50E4C841",
+ "d_a_obj_crvlh_up.rel": "904E695B69E01BCD9C9F3113F847F9864AC575B6",
+ "d_a_obj_lv7propellery.rel": "FB7A62ABDBDC70E9E2F819596432D6384D442ECB",
+ "d_a_obj_digholl.rel": "C1C9DFE76598206DDF99A635334AA8BCCC25CA59",
+ "d_a_npc_zelr.rel": "4265B6402F946CC23BE042495728BFF8800B1A68",
+ "d_a_obj_swpush2.rel": "308244F76A24414F413A968591DBF453C7E38A74",
+ "d_a_e_mb.rel": "77B48EC99B555EDE925F34151A4E2069B168ACE2",
+ "d_a_tboxsw.rel": "CC5F11672A277D71FB2FFDD1ACB8A3B4E89F41C4",
+ "d_a_npc_ne.rel": "778752B1A0F5F3C4D4379256FF6633D970B331FC",
+ "d_a_obj_warp_obrg.rel": "468638E68C9835A2DAD0986202A877503BF96F3D",
+ "d_a_obj_zrturararock.rel": "5738A32F13A84F0F6638D22C6576D84B823F742E",
+ "d_a_e_yc.rel": "624214752285FA39E6A7C82FA2C3C2A49A6A7FC7",
+ "d_a_e_pz.rel": "5BB83AC854E7AFB8039609A897AC0AC4EC2CE92E",
+ "d_a_title.rel": "5D825D6ED7E73342B494CA6D48A35654D0229797",
+ "d_a_obj_scannon_crs.rel": "DA6CEC7110B0517134E7F47D8C5DB856E44475F5",
+ "d_a_l7low_dr.rel": "5768F0EC62728FC49C6790E39F139CC50EE5847D",
+ "d_a_kytag12.rel": "672076C0D23236C85827CC19432C6A28BB55FCA0",
+ "d_a_e_mm.rel": "7A416CC1454B0C287AB3028778174843A4B8B966",
+ "d_a_obj_boumato.rel": "567132294F46E451A228CB5E27D5441C1002E725",
+ "d_a_obj_lv6furikotrap.rel": "F9FF87DFBF9F140AED458D87312F0974D3F088F8",
+ "d_a_npc_kasi_kyu.rel": "F48395096573E20B0FBE2F52D6FF1F4B66C717C7",
+ "d_a_e_db.rel": "D06F3A59774AB3B3FDD83FA0FC0E06E8861F3CF8",
+ "d_a_e_rb.rel": "0BB6451A88CA5DCAEFDB992805A58551D59E0D0E",
+ "d_a_obj_firewood.rel": "06A80D6623EF6735ACC6501CDC4A09CFCA801CA6",
+ "d_a_obj_wdstick.rel": "5D0571B7607F4909C43220960C435D2BE99EF055",
+ "d_a_npc_shop_maro.rel": "4B42E7D5E7F474658F4D8B3DFDDF24645F4FD875",
+ "d_a_e_s1.rel": "54A51A17362CF2ABB012A07B20CA8E95E07F8E0E",
+ "d_a_obj_lv4pogate.rel": "BB8ED430CB005B5B5D8BA9ED2AE835BC838B1DEF",
+ "d_a_e_gm.rel": "376AFBC36F9D98539D9652CA1384AC2DC1BFCF06",
+ "d_a_npc_grs.rel": "5059CB0B6CA6C5BC61A4CBC96B06A59C113C40B3",
+ "d_a_obj_rotbridge.rel": "875313B7CA3635CD58DAA4135F2DBDB78B8C671B",
+ "d_a_b_gnd.rel": "CE8C619E7EA74937899644DDDBB6E8000ACB878D",
+ "d_a_talk.rel": "763ACFA67013F425C1CC3AA61E8106F2E87FEFF2",
+ "d_a_tag_rmbit_sw.rel": "FE50F79C70AB23E7CEF7FAE868646FFCFAACBD79",
+ "d_a_npc_lf.rel": "B44FB518CC1EFFC8BFAA260DD7452C18383F3DB8",
+ "d_a_npc_wrestler.rel": "944D1EF9969538EBC9E5ABE480AA1D804F82F6A8",
+ "d_a_obj_bmwindow.rel": "010D8B8B21D9024C380FB88F4EE7E526924BA991",
+ "d_a_obj_smtile.rel": "1C89D6D9BCC7F624D05382BC902145D6C707BE55",
+ "d_a_obj_sm_door.rel": "96167AC7CA08DB0678535F49085553FFA3DF7B4F",
+ "d_a_b_oh.rel": "C1CB6164398557175F2448C01299453C8641A76B",
+ "d_a_obj_lv6bemos.rel": "1F98E050E735B69B15C572DF27B75B4E2D242504",
+ "d_a_kytag16.rel": "DBAE37DF1335761A976B56D8D0CD3AC11ACD8D77",
+ "d_a_coach_2d.rel": "F6CEED40911AA80091B17CB68B0A356E315A85EF",
+ "d_a_obj_lv3waterb.rel": "8D3150A764110CF258705AAC9D22DE0A81535304",
+ "d_a_e_sh.rel": "F0CE8DD759380C7525ACCBC6EAF10B9AF5DE563C",
+ "d_a_tag_twgate.rel": "3780DBB8F401BE0C399A759C964D8E28A022AB8C",
+ "d_a_npc_grm.rel": "3512C777B7300DCCCC59D5C7A3F894CCB0353491",
+ "d_a_e_sw.rel": "F21CE3FDBC102F809CEB8843CFFC8487EBBF685B",
+ "d_a_npc_yelia.rel": "E36BC51C80F25F9DB7BAA208D1D9D0B4C1EB3D89",
+ "d_a_npc_bous.rel": "F4C9E0D68907D38F86863F6795DF973FEF364F87",
+ "d_a_npc_p2.rel": "62A7793C9B38236A7A6848180DF0F7B93F0CAD7E",
+ "d_a_e_ph.rel": "A8A038F3EF4DB862EEBD0A8F258C8F9950D5862D",
+ "d_a_npc_jagar.rel": "EC8BB1D14F8E6CB01458FA714F0FF98763F5F48E",
+ "d_a_door_push.rel": "96AF217AEAC7D277F62E62F7402A65EE68F45F0B",
+ "d_a_obj_swhang.rel": "9FBE5A6CA15581C3BD765FFD839219E067B5CC81",
+ "d_a_obj_kjgjs.rel": "70482127A36280A0723AE7DA76C42F5678606DBA",
+ "d_a_obj_drop.rel": "9C6223018EE1910993EDDD19CBC1A676D737A902",
+ "d_a_tag_smk_emt.rel": "0096760CCB4E2BCEA7B66EB1B27CC930055CB910",
+ "d_a_tag_csw.rel": "888EB195989DB928389A3E5F703CAF8DE6486EDC",
+ "d_a_obj_chandelier.rel": "E8BEECFE7E519B95ED7F52DFECC70DC76945DAED",
+ "d_a_npc_inko.rel": "63D504F55EA92FCA0AE5B8B904E4EA4AF2C39B53",
+ "d_a_e_df.rel": "0E92D56E50DB2EC8EB265629E86DBFE180D30F5F",
+ "d_a_obj_lv7bsgate.rel": "494004BD260D2D7FAC4FD8F34E13B66148D55C0A",
+ "d_a_e_hzelda.rel": "FBC2C3CF5E04C7D50E0FB0A1B88A3170EAEA52C6",
+ "d_a_obj_volcball.rel": "269AC41E0D2FEAC375D60BB8002B347130362CD2",
+ "d_a_npc_len.rel": "347F617E71BC882682C3B32EDBE05D2D2283FF89",
+ "d_a_obj_heavysw.rel": "CBC6C1903EB978124CFA2138A433BDC6831245E5",
+ "d_a_obj_ikada.rel": "E6B780F90357F10F6AF2F5F19FCF9BDCE89BE83C",
+ "d_a_npc_moir.rel": "D87686CFFC9973CAC00971D2AD163385AD012291",
+ "d_a_obj_cho.rel": "7314A81F6DA6BA84E8A14EF2889870ED8ED89CDE",
+ "d_a_obj_hakai_ftr.rel": "0FC8A311760A0FB66E9C1B9A5C8CA18F6A4A109F",
+ "d_a_npc_tkj2.rel": "6A48547BB3DD2CA14BCEAFDA704C6E1C041BE02E",
+ "d_a_npc_gro.rel": "1F8BAC5D29C8E7CDE938A8E806BAB8C708FE5232",
+ "d_a_obj_stairblock.rel": "B75D7A31AC36BD2975C9152460F498AAA2A0F354",
+ "d_a_npc_kasi_mich.rel": "E6375B3902E7E6A0140F301847153130DFCB8CE6",
+ "d_a_npc_the.rel": "8EB0711679A89C792C5C7A538CC11CC20D20BAB1",
+ "d_a_obj_brg.rel": "D1966C75EBAA8F74BC77A6AD90732E78F878C97B",
+ "d_a_obj_lv8udfloor.rel": "529E48FC87176D84E634D77DD32155C1ACA65CD1",
+ "d_a_npc_df.rel": "858262B644F0AD3E79C41EA6AD3CCCD1DF3D8AEF",
+ "d_a_npc_uri.rel": "0917529F1B2D55B6C98A54FEB87799C512C08C5C",
+ "d_a_passer_mng.rel": "40C8B5EB44C46211451A007FEEB63059A273AEA7",
+ "d_a_obj_zra_rock.rel": "2DADECA1A7F5EA8705771ECE7C0DECCBFCD2D411",
+ "d_a_obj_mirror_chain.rel": "BE5F1E1631317E32F3ADE0C7636136C96489B280",
+ "d_a_tag_pofire.rel": "753B3D4F6BBA0E91AA09208075831AB3D9924870",
+ "d_a_obj_kago.rel": "9CD026746F50AB7DC810EBD022DD89BBD1AB05A8",
+ "d_a_obj_ice_s.rel": "D2E815823DCDC802E417DFB94CF92DD536A2CE65",
+ "d_a_obj_sakuita.rel": "4A68E0150C4AB0C545321E283FB83272ACEFD97C",
+ "d_a_tag_shop_camera.rel": "E7A4EC71A670FF0CB15B3A04B76C854CE5699718",
+ "d_a_startandgoal.rel": "44704ED06BE87FBE3E0CCBF2FDA36012EDE71755",
+ "d_a_e_sg.rel": "04971D6F0A78ED63EE77ECAC4FDA3386D6B46071",
+ "d_a_npc_grz.rel": "0E6BADFC97CE3AD244AC4ED0F4BEDF99465F7BDE",
+ "d_a_npc_prayer.rel": "0B8BE32F32CD36F1A63F5A102AE3324B61B2C940",
+ "d_a_obj_ystone.rel": "1C0AA0ED79E0427A0449A483478D63B6FA8BF320",
+ "d_a_obj_bk_leaf.rel": "D0A037C463EA1E6EB7A682CD3B67E6FC22FD4E4B",
+ "d_a_obj_flag2.rel": "E964758B4BEDB2108A29C26D02B5B0527C069884",
+ "d_a_e_kk.rel": "2992F2CF9DC1B096475E505C2730F06C11F5969D",
+ "d_a_obj_flag3.rel": "A23D52B33EC7CF517EB798B0C1645568812793AD",
+ "d_a_obj_watergate.rel": "18D78B492EFCD05EAFB54460858CCCFA401E882E",
+ "d_a_kytag09.rel": "0FD935A3F4E8763BC298FA08348AEC446BCFC005",
+ "d_a_e_yr.rel": "34484ADE45E8AA7C243FD1A1302EF8C67284732A",
+ "d_a_kytag07.rel": "4A77254665CE96D64BE725C2EB8547AB24DEBDEC",
+ "d_a_obj_lv1candle01.rel": "4657CBE18EDB06D0F95788AC1B9F5D8AE7362488",
+ "d_a_npc_shop0.rel": "C26638E69F35849A054BAB7A3BD6C24D6923EEF3",
+ "d_a_e_bi_leaf.rel": "C4060CEF650FA8BEA6D3B1087D1BF9C9A20A529D",
+ "d_a_npc_bans.rel": "C24090A559ED9C3DB43E8BE5D198D801AC435AA8",
+ "d_a_b_go.rel": "C4959C479128C4BB0E12B97439ACDEAF51716CD7",
+ "d_a_npc_midp.rel": "0EAAD71622D7E0C0D8E37C7F22925BBA73AB0751",
+ "d_a_horse.rel": "4E203991AB751AFF2199C20ED76BE74310AEB4BF",
+ "d_a_tag_wljump.rel": "CAADD767731433AB4D6DC9C39E1858273DA996DE",
+ "d_a_npc_bou.rel": "315BB79D1F7D5619321F9DCF29E91CF1C1F6EFB3",
+ "d_a_e_vt.rel": "3C74A0E6208F16AA0EAA467E39F7345FE7EB22A0",
+ "d_a_kago.rel": "D4AEBEE8299C2EF0020DB654AB4369433E0F2D80",
+ "d_a_obj_pocandle.rel": "3649EAA8F60539CE7E0DBCBBE79D7DE30CDA12FA",
+ "d_a_obj_vground.rel": "469FBAB3BB2516AA28664FC6D2DD9353EE41B9B6",
+ "d_a_npc_hanjo.rel": "D3E410299B126532D45DF65D914029BEFA943129",
+ "d_a_obj_grave_stone.rel": "CAEBCE00942CF3D295AEBE0D1C6F96D8B524F228",
+ "d_a_e_th_ball.rel": "BBA181DF8125BF257EC17A9661C26C19525C0C70",
+ "d_a_obj_lv6elevta.rel": "60FDC030610AC9C0C847BE318C29DF522C611867",
+ "d_a_npc_du.rel": "852B2D34FA7BB27B4C456E8EE591B6955635C58F",
+ "d_a_obj_bky_rock.rel": "24B08DE6343F5055A26BEA76E08B9E92F59D7883",
+ "d_a_b_zant_magic.rel": "55B92F2FE90A67CE14752E2614048127BDF3F8FD",
+ "d_a_obj_table.rel": "A321ECC34662C8F084BED14E03BBF56A2D0ED9B7",
+ "d_a_npc_sq.rel": "52E4548D910B1E3104D38ACFF2A1FCE973B7D939",
+ "d_a_obj_gm.rel": "783401E99CD2659CFB538F773B7DEA2D84188374",
+ "d_a_obj_szbridge.rel": "5B13BBD455BBA01F20A095346D6078742AD88E40",
+ "d_a_door_bossl5.rel": "3667A291AA36BCD9A33BB37242209082AEE78A62",
+ "d_a_npc_zrc.rel": "089B3C7EFCF920718D8EF4C66D6B6E91BB5B5D3A",
+ "d_a_obj_lv3water.rel": "28912A6E826E70DA0E71BDBFEAA73F6154356A93",
+ "d_a_obj_lv1candle00.rel": "5F8C9C5CDC98D0525D70293A49B1F0B1F8638391",
+ "d_a_npc_kdk.rel": "28CB531A70631F3B94A8D8DAA54587DA255510C1",
+ "d_a_e_mm_mt.rel": "6E4A26884F660B22286024668E14B5FBBDA2B338",
+ "d_a_npc_grc.rel": "7A5FB80D806EDDF7C20B7051EA25F5E789DB84E4",
+ "d_a_obj_sw.rel": "9F2A201E5D576AEF3EC9007937C932D8252379CE",
+ "d_a_e_bug.rel": "AFF95D5818061CD47EDCA56F58B77A9B09F2E6D5",
+ "d_a_obj_lv6bemos2.rel": "6A4D3E21E582EA923564DD8A0319441980698394",
+ "d_a_obj_kazeneko.rel": "1ADA72286D3B56918BF472265F63425E439AB51A",
+ "d_a_b_tn.rel": "ADDD7D14D3B23023C90C112B22D669248CC24E49",
+ "d_a_npc_fguard.rel": "955A0AA7527C4AD6AA99967046812F92119F2048",
+ "d_a_obj_fw.rel": "EFC6E7CAA25EAA2C69868132D7BEC857C3133278",
+ "d_a_e_sm2.rel": "9E452082F591FB8B81C54DD4B89430432E2FC12F",
+ "d_a_tag_kago_fall.rel": "3D650786D9ECABBF016D2EF9FD6F56B51E40C9DB",
+ "d_a_obj_kamakiri.rel": "18C316BA8C5E2B1D4C23B90CED94901DA94A32CA",
+ "d_a_npc_ashb.rel": "65E7DF6AD0057736409AF0B9F681AC14F5E264F8",
+ "d_a_e_ww.rel": "8E99CABDF7CD5BD518E1F292B5014DB7199398A6",
+ "d_a_do.rel": "A5A21BEDCABDBCA100649D263A4BA52471B6C5FB",
+ "d_a_obj_lv5swice.rel": "970B54E52DC0263AB7E9077F771649B95274F203",
+ "d_a_obj_lv4digsand.rel": "481C498D70C653514C3E784952710312936ADEC6",
+ "d_a_obj_kuwagata.rel": "3E40D37275448FF27BCD8EC6D941665C95A9F908",
+ "d_a_obj_food.rel": "6DE245B93F471A65F80E2598495804DC25B37530",
+ "d_a_obj_hata.rel": "DBBCC363C61D212B7EA7E73F4E3635E7FE21D96B",
+ "d_a_npc_kn.rel": "D90D5A3E097AA14236A4573CE60BBC432A9FC106",
+ "d_a_obj_pleaf.rel": "29C651E484A27A6C1BDD9CA3040BC5F427B25900",
+ "d_a_obj_dust.rel": "21F40C3835B4A355BBB15E9196CB9CC234E0838F",
+ "d_a_formation_mng.rel": "B9C69B478A0D75AC3CE4061528823FF88C8C849A",
+ "d_a_obj_firepillar.rel": "686F236D8F6B5DCFE88B615D032F01394AA2986D",
+ "d_a_door_mbossl1.rel": "43527862E8878D2F6192F1E947DB351F91C2376E",
+ "d_a_obj_lv9swshutter.rel": "91D6B4E916E77413CBEC9C1DDC93135634C57B40",
+ "d_a_npc_chat.rel": "81871F89AFDE615518690CD906294F0642E7AEA7",
+ "d_a_obj_lv6swgate.rel": "2F572729258E3E90A33F6285C08514A0079D163C",
+ "d_a_npc_ash.rel": "5CC511C732873509024FDF1BF7759641610864C9",
+ "d_a_obj_kwheel00.rel": "58B43E9DC920D7AF79EFFB750FA104F2D9BED1A6",
+ "d_a_obj_crystal.rel": "2DECB0B34B64395F4AA515DE8A4A7407A2759417",
+ "d_a_obj_hb.rel": "3665E4CEAFF44720CA854AEE74EEFD1447E13931",
+ "d_a_npc_tr.rel": "EA9F95EF4857902706CF32453C51C4FC93484CF4",
+ "d_a_e_dk.rel": "DCEDA0449B73B48232B35B246B17EA13DD8AED46",
+ "d_a_npc_blue_ns.rel": "8C77D229853738C5D766C405645C8E82A2B698A4",
+ "d_a_obj_swchain.rel": "ED381DD3BF359878C116FA1D6EB08AF039576979",
+ "d_a_npc_yamid.rel": "E4E43A08DF9F2BE1ECBE77072E14AB58A99A3D58",
+ "d_a_npc_zanb.rel": "A1380E0C3CD3C35E61CA59E11FE027225B003635",
+ "d_a_e_dd.rel": "38AC18419F33670D4550E1E85992213C798949DD",
+ "d_a_obj_sword.rel": "D01643C4444121A8EF8B5DF5F3191C949E808451",
+ "d_a_obj_shield.rel": "C12D434E70652B723488E614339363248CA1981E",
+ "d_a_obj_tmoon.rel": "FB1E8D6AD5F8770F7B8CAF6734BF94E5E3B7AD2B",
+ "d_a_obj_lv7bridge.rel": "F4AAFD8CD7FE7F4C02AC09D9813CED1EC6397CDE",
+ "d_a_e_ba.rel": "AE75BDBA3AA850913C32821314409B9F6F0F64DA",
+ "d_a_obj_lbox.rel": "44D67C73086A686EE7A1AD691B79BF810FD7A669",
+ "d_a_kytag01.rel": "7D32FA40CA609627A4083332FFDE0076FF1CA5DE",
+ "d_a_npc_shoe.rel": "75972E0AC5B38BF824F674CD66393460BCB4AA66",
+ "d_a_obj_lv6changegate.rel": "30AC71524DED4EBADE99B18D378ED6E77ECC2ACE",
+ "d_a_obj_sekidoor.rel": "51BDC2618F8D054DD08063BDB72385883F8F62EA",
+ "d_a_obj_pumpkin.rel": "FFD7E1636878E9F43DF25D90A987E8D47510719B",
+ "d_a_b_mgn.rel": "A26FFE08227C62DB25816E997C257E1847856C21",
+ "d_a_tag_yami.rel": "CD7DC3140141D4F6A937175DA1FEC7F04190FB03",
+ "d_a_b_ob.rel": "FC958F705DF7457106F3A5CD0C74FB3BFF40458C",
+ "d_a_e_yd.rel": "750F0B3E7DBE50B16DCB04FC8B4DB71A733510A8",
+ "d_a_obj_riverrock.rel": "2D4E87DF6721ACD07A8E43C5E8165744E4C3E4FF",
+ "d_a_obj_prop.rel": "C9BAC70E74CDE774874745D928F8D5257CBF29E3",
+ "d_a_e_kr.rel": "7742E806CA5EC0EFD41822AF7BF3E122DC6729C9",
+ "d_a_npc_hoz.rel": "1171BCA2DB9B73AAB7784DF2DABEE74D578436C8",
+ "d_a_e_hm.rel": "5ED1B430FD1BE88652E8116E3D0B5B19F657F422",
+ "d_a_l7op_demo_dr.rel": "10D761B98E18E447D48EAD97B762B26B4FA55DFE",
+ "d_a_obj_lv6lblock.rel": "408CA5D602EC0CFC2CC240D91B96CDDF9B14EE08",
+ "d_a_e_zs.rel": "9F9A22537970420CD8A3686F40B9FF6C90C2CC93",
+ "d_a_guard_mng.rel": "2516C3D935127BD80099A7C77CE63FF2ECAABEFA",
+ "d_a_myna.rel": "147159E931F6D9D5212A990E2F7BDC530BC3DFC3",
+ "d_a_npc_pachi_maro.rel": "9BA517B048EBA36894F6DE14EFB01A7FFA8D9660",
+ "d_a_obj_swballc.rel": "DED5E596BB913D865371ED558F50D99EAF630104",
+ "d_a_izumi_gate.rel": "2710FD69B5952F108A3E4DD86A20E717A512652B",
+ "d_a_mirror.rel": "857EDDEF52FF4913172E7A0E4FE5D8CA0E5DC4C5",
+ "d_a_obj_lv4candletag.rel": "D880E6A26B6353835C6A7CE52BC90047B3D21B28",
+ "d_a_obj_rw.rel": "B4A7D27A83E4E5EB8F11BB94D4D6764006B33D90",
+ "d_a_obj_syrock.rel": "9C677E794D4BAC7C9C873431D7615267C0887E24",
+ "d_a_e_hz.rel": "4E2648C9A4D7F983AD2F9C3E62035CE80C929091",
+ "d_a_npc_seid.rel": "748F68425AC9D0BE63D05F5D28D6ED9AF58CD7D8",
+ "d_a_tag_lv7gate.rel": "629445AE36F79CE718DCDA7CF170B6179FEA8925",
+ "d_a_kytag08.rel": "34F4886A03A2337F6C5F943EEF8F433FCE566C74",
+ "d_a_e_is.rel": "5F14FDFB70D8C54105830CAD0C3AF470683461E1",
+ "d_a_obj_bed.rel": "F0FB599FAA59D45F020486652A6D0C3F4FDC7B55",
+ "d_a_e_st_line.rel": "1EABBB7CA1F9D4AF7BABD899D39DD5E76786804F",
+ "d_a_npc_pachi_besu.rel": "E38F315FAAC4E9A673C459CEAB71DF68BCB029E5",
+ "d_a_obj_thdoor.rel": "3D3193784C69E450BD5B031A7D8D83C088FBF623",
+ "d_a_obj_kag.rel": "46C44517440489F056C3ECD24DEB6C766E238012",
+ "d_a_obj_dan.rel": "7DD1357FEF8C100530199EDF76D1EE9A5899E09B",
+ "d_a_npc_gwolf.rel": "C9A73DD7D1809E6B3698AD46EAE70CCDB736DB6D",
+ "d_a_tag_arena.rel": "7049F905A4D7B8552931F6C047C99C31D76125CA",
+ "d_a_cow.rel": "33FC1A29393B38E05B219C36E6BE115C390F04D9",
+ "d_a_e_pm.rel": "8C29B8A90FFC7505DB1DBCC4C2BA655EA5C6130B",
+ "d_a_e_oc.rel": "B2D925DF6599A705FB3D20BA7DB164991E14AF4D",
+ "d_a_e_rdy.rel": "2364728BF05633DB533698A221190541B035A1AF",
+ "d_a_obj_magliftrot.rel": "433CA28006AF27F2DFEFCA6EA3EED45CF5A52D3D",
+ "d_a_obj_lv4slidewall.rel": "BE8AEABA797494C414E12B884F0DCD48D3AC7164",
+ "d_a_e_zh.rel": "BB1E7CFDD65A425E9D8EDE8541959F531AA6DCE7",
+ "d_a_obj_toaru_maki.rel": "7C3C1DD4C47D69AEA5988B1529F44D40CD543918",
+ "d_a_obj_yel_bag.rel": "2BDB911187A2E62EE0327158B7C20BF63AAAF4A1",
+ "d_a_obj_hstarget.rel": "86CAEDDB6398F2BEF95ECE12BB7D7D7E7284F9BE",
+ "d_a_obj_digsnow.rel": "0C471AB3E846EC4D82F61DF8B60413EF6AC07388",
+ "d_a_obj_bubblepilar.rel": "28C46C35A8BC0E85AEE81B036BC6A9BD984FFF24",
+ "d_a_obj_well_cover.rel": "CF15ACDB7386496EB2E4F166A4E6CB9F2E62CD9C",
+ "d_a_obj_mato.rel": "79FFCBFFEC6CA9AF067BEC16E1238EB01AE10E33",
+ "d_a_obj_lv4railwall.rel": "8C0348948D94650B4A091779CEC78C64B7020C71",
+ "d_a_e_hp.rel": "0786282CF9C9EAE1C0F3D34E8ED15646299D5F2D",
+ "d_a_obj_gomikabe.rel": "CF32E0BCE5C74A4D5008D7A2CB0F0D8CAABAA288",
+ "d_a_obj_lv5icewall.rel": "EA56159D3388F8B86D9D5195AB262D3C98CD7BF2",
+ "d_a_obj_lv3watereff.rel": "F0971936E0A9CAC2735D469B01CCA41BE0F5E411",
+ "d_a_tag_sppath.rel": "0FA1241FEA4975F0F1AD2256FD74256FD3E461BD",
+ "d_a_tag_shop_item.rel": "CFF8F55EF5C365C58B81FD7AA5A003EDD90592C0",
+ "d_a_npc_grmc.rel": "0DFBB1101BAFF311C1A3D0DC60A97259CD6605FC",
+ "d_a_obj_kwheel01.rel": "C06A5219ED7A8EDBB0A77A67F68C3173D52730B2",
+ "d_a_e_sb.rel": "122E3A50B45B214267C1836D7D2F782AEC724DD4",
+ "d_a_tag_theb_hint.rel": "282178DFA0D90F418457EE32D950EF5A78FAB330",
+ "d_a_b_gg.rel": "CBC6AE00BAF57BD2E9BE97BACC187485360A7524",
+ "d_a_obj_firepillar2.rel": "C1514151905A84AE316A3FDC5160E4A3CA0B36BC",
+ "d_a_obj_h_saku.rel": "A52B6FC32D356E19EE39ACFC6C4AFB6AF4E81FAC",
+ "d_a_obj_warp_kbrg.rel": "2EA85C90BFD096D31083C2486176D56785BD8BA0",
+ "d_a_npc_fairy.rel": "771446C6ED4E459034ECC090773DD6FCB44944E2",
+ "d_a_e_mf.rel": "209464B96ED1E8E2C59A48951D97F8357DF01256",
+ "d_a_obj_so.rel": "1BCD37C86F820A581480D509365508AFCB28DDFD",
+ "d_a_obj_kshutter.rel": "F225EDB33E365FFD6F79F7E8255C0F6149D40544",
+ "d_a_npc_gra.rel": "07BA9D254DF9CB6B96B9DE08B1929D084A4B99FD",
+ "d_a_npc_tks.rel": "AF8F34B7E784C3A8EF2D3F4D29B1DEC76AD423C0",
+ "d_a_tag_mmsg.rel": "AE37CB4F62A58688172161C02C147D3843CF9804",
+ "d_a_obj_lv4prwall.rel": "5E4527488F668088EDE07AB2CF4713810C9A8C62",
+ "d_a_npc_ykm.rel": "A3A6F88D8F58989DD9E724E18DBF9293230B49C6",
+ "d_a_tag_lightball.rel": "50D54005A44DA8F50CB44472DC98C9286FB68F22",
+ "d_a_b_ds.rel": "EE8AE7AB1A873A736FF2929DF326D2923FE4D148",
+ "d_a_obj_ari.rel": "A3F6E97A7CB56BC402816979304E06A9D8877772",
+ "d_a_e_gi.rel": "11CC173A4243E2EFE768004386F997953E469D98",
+ "d_a_npc_clerka.rel": "6983F09621B04D9D0C8322F29C8FB4D6F2F5080A",
+ "d_a_e_bs.rel": "04E8A928E0078922D2D0629656477D1B4DC0717D",
+ "d_a_obj_crvfence.rel": "C86BAE0E3DBE7BE7E597A8B393B36F8C6798580C",
+ "d_a_e_rdb.rel": "74FF8015DCBC61A0B1D0124C879C80C3C4C72667",
+ "d_a_obj_crvlh_down.rel": "6D602998612DAD842948EFFA4EAAAEE06A42935F",
+ "d_a_obj_rock.rel": "20115D37BD1A8669C4719301E25F9DF67860A9E2",
+ "d_a_obj_pofire.rel": "34E1394FAFA19C4BF21E048DC32D824A084595D2",
+ "d_a_obj_stone.rel": "67B3B11BE686377489548F19915F499643507042",
+ "d_a_obj_lv4gear.rel": "0A93059311DF8AB16DC9A404F3DAE01CA47F6EC3",
+ "d_a_obj_wchain.rel": "E25879F6D746E7C70D021FB6D881402B219F4279",
+ "d_a_obj_spinlift.rel": "D4EDA5F1AD329E65B6D727C69FEDC617E142F0F3",
+ "d_a_tag_pachi.rel": "15170A513C91307CE76644466ADA91190DE2B4F0",
+ "d_a_swlball.rel": "8094F348090A2D3F63CB1404E10C8E7CA46A0A2B",
+ "d_a_obj_lv3candle.rel": "21A9C38704D93AEC5E06A8F34C3CA0CECAD07CEA",
+ "d_a_obj_smw_stone.rel": "21A849A0357996AB647A412F64238691100D56D4",
+ "d_a_obj_mirror_screw.rel": "DCD917AD581DAED8FBF4DE4FC29C0B9C9EFA59D8",
+ "d_a_npc_coach.rel": "060698A76881879632EBDD25C80F80CA3D4C15DC",
+ "d_a_npc_worm.rel": "63D7963189A5FFBA8849B800E39D0EB44E37FC60",
+ "d_a_e_yh.rel": "32BD605FD49C0B217B96AD9ECD6E233703566BD2",
+ "d_a_obj_groundwater.rel": "BBDE04D5DC9D20CCB5607B16BEDA4821EEC2DD41",
+ "d_a_e_kg.rel": "0072BEBFBBE4DE2CFEEAB234CB9AD1554BAE4E9E",
+ "d_a_tag_lv8gate.rel": "FBBDB788FE6A127F26EF0584461456E65A07D0C1",
+ "d_a_obj_stopper.rel": "9F7F45C76F398B68B624CE18E79A3C1B938BC09C",
+ "d_a_coach_fire.rel": "1663D78E789846FA43CDF8C86D3F6A5AC272159A",
+ "d_a_obj_ten.rel": "DD99A1444A2FF29D9AA8E8F8088D629482638739",
+ "d_a_warp_bug.rel": "2F2E88881510F16895F65A959792712E3DF9E8BE",
+ "d_a_obj_mhole.rel": "369B1DA1A6EE019371941915040E0B027759EC3D",
+ "d_a_tag_ret_room.rel": "4A411D05DD2C079CBA8D3DBE64FD4EA491B398AD",
+ "d_a_npc_grr.rel": "3D19664843A61365A7A004F5669B825ECE2D1679",
+ "d_a_tag_waterfall.rel": "BD3DF27A66051325C551533B0D9023CBA6AD3E8C",
+ "d_a_e_gs.rel": "784C8F868A0D60A645DDA49E14EE8CFA0272CC96",
+ "d_a_obj_takaradai.rel": "EC2A5579C6FBEE5AE59B8AC7BF17602BB2963D29",
+ "d_a_obj_ice_l.rel": "E483FB2DD764CD2C0A20388E4D7977B3AB4BC456",
+ "d_a_e_fs.rel": "D914B0EEE912D08183BF1E5A47FDD138400285D6",
+ "d_a_obj_fallobj.rel": "1D4341B1F9851F851633BB72F88267F6A2E8A933",
+ "d_a_obj_crvsteel.rel": "FB613745F6712D06CD90B1EED03246ECC35BBD82",
+ "d_a_obj_lv3water2.rel": "25311FD64E35E9FE83077DA3000188C628A9D029",
+ "d_a_obj_crope.rel": "DC4300A41663850324AD72298F004ABAA47CF5FA",
+ "d_a_obj_sakuita_rope.rel": "2780457F8D017D649005FF432518D893781A520E",
+ "d_a_obj_thashi.rel": "2A85605C4C3E2CFFDF6F737290F3CD5CFE6FF8E4",
+ "d_a_obj_itamato.rel": "6E17719E2E831D6FD34C069EB6CD3C9CF9306819",
+ "d_a_obj_knbullet.rel": "CF0E62EB9AD9D57B337B86E2F26EDBF8BD5DBB11",
+ "d_a_obj_wsword.rel": "ECD038C7276C21C784C4707F21296C2559CC844D",
+ "d_a_e_wb.rel": "559E88F6AF26017F179C8940CD37037A088D97D8",
+ "d_a_obj_smoke.rel": "1117BC92D9B1D8A9538D10072BA52744477A85C9",
+ "d_a_obj_swballa.rel": "0002C394755A39468C9D028295A3A273512E89BC",
+ "d_a_tag_escape.rel": "396DB520690950691CB28F00582637FF9F6E18A8",
+ "d_a_npc_clerkt.rel": "48ACD1CAD599BF51AD939A574319608A77D91C8E",
+ "d_a_e_fz.rel": "5A3CAD6474529556B694379D47FE1EB58AD18E28",
+ "d_a_npc_grd.rel": "770F5ECD8A508A2C9CCBB46B20B6771626E6CABF",
+ "d_a_obj_cwall.rel": "6FBE5964CAE388026838CD4917EC05BB46157164",
+ "d_a_npc_theb.rel": "5A8E00B1C6A88A2D9299D5FBC9F36360FA880EE2",
+ "d_a_e_mk_bo.rel": "12D4F2BD0A1B252141B80C709445D579FD80D5A7",
+ "d_a_obj_picture.rel": "CF49999B47D73343863D57907F926D99E320FC9A",
+ "d_a_tag_setrestart.rel": "0E7AA61934F1D91D58A86A8062C23782C2FE41EA",
+ "d_a_tag_river_back.rel": "CC7B78D9A68C6D11B38F05ACE78D50E0ACFC3BA0",
+ "d_a_obj_rottrap.rel": "C7EEFCB168FAC2D08F151D9770D0100139D6E1AE",
+ "d_a_obj_swspinner.rel": "3CCEDBA2AF37F1C94E1187FE02D0113641BC927C",
+ "d_a_npc_toby.rel": "FABD4FADA4526BC3E7030575621BE07C47E77704",
+ "d_a_swball.rel": "3BD51AFD5FF82E4A897CC38C22B2B1F970875E0B",
+ "d_a_tag_qs.rel": "1237A4A6F64940C9C43E51E2F10613B32936422F",
+ "d_a_obj_lv3saka00.rel": "F25D8701FCF116BAE7A6C54B80CA19C2CCCF6442",
+ "d_a_obj_bmshutter.rel": "4D7E4316612FB5D01BBBF34F921B5E1A2FC56D16",
+ "d_a_obj_kkanban.rel": "76D0B401BE39DC1F18F03713D837831905C669FA",
+ "d_a_obj_gogate.rel": "0EC0E55DAB5A085566B9904DE0CB618FA0601F22",
+ "d_a_obj_lv8kekkaitrap.rel": "177631AD03D1EDE1D846D86A78612638D8F153B8",
+ "d_a_e_th.rel": "FED72D57A9D21690EA444BA97D4708E05519D16A",
+ "d_a_obj_lv6tenbin.rel": "65D974EE2F6ACCFAFC63C6D1839A2A585903CDD3",
+ "d_a_obj_kbox.rel": "01904FC16812D689B7494DA4F8FE7064F0E7B212",
+ "d_a_b_zant.rel": "AB4CFB83A4A9CCF48AB12F4A71261B21410B0417",
+ "d_a_npc_yamit.rel": "053C4338FD62CC42236C53D7E64F190E4B2A81BF",
+ "d_a_obj_bbox.rel": "F7DBDE0A7354BCB1A87C0D2DDEC7BD48A80B41E9",
+ "d_a_npc_ins.rel": "5E949CCA4297B56293AA95873465147E8402145F",
+ "d_a_tag_gra.rel": "38B7C6274C9D409CAA25593C7F38CC55AD100F3A",
+ "d_a_kytag02.rel": "B45D727581DF37CAC99AE6DABBC9D6614F953747",
+ "d_a_obj_bombf.rel": "C636967F4DDA971C4AE8F81BA2A586AA141E9BCD",
+ "d_a_npc_seira.rel": "39A4E81C12EA364F19D416F15231AEB73B0089EF",
+ "d_a_npc_post.rel": "E3972B3E38161614E2968C66A99409FC3BE22A1C",
+ "d_a_obj_key.rel": "DD3C3C14EFD09F02A423AD10F3F1A4E90204DDA5",
+ "d_a_balloon_2d.rel": "37C0946ECDBC16218686F213E7F86B80AC426270",
+ "d_a_tag_ss_drink.rel": "5AB974F71C49B42C5908BE1A7F0DC5AA8E9E8EA8",
+ "d_a_e_st.rel": "98425104B2981B21C725866A3F8ED7C381DD65FE",
+ "d_a_npc_passer2.rel": "10A8C2E20959063469488C3DAF5ED983CA512ED9",
+ "d_a_obj_crvgate.rel": "AF57D1CB4E497AFEBCC91D803FA4BF0F504DD9E9",
+ "d_a_obj_iceleaf.rel": "69669F62B8D90082A5C06FFA36E91F30BFE9C06C",
+ "d_a_obj_tombo.rel": "05FEEA079BE389BD4A629FBC1EC42D89AC8F7723",
+ "d_a_e_hb_leaf.rel": "C692059934A329764BE53A3CAF480F214A2897A5",
+ "d_a_tag_bottle_item.rel": "A4638434A0319C493DF858D8E918966A275837B2",
+ "d_a_obj_lv5ychndlr.rel": "83FF4A28494BBBC42DF23B7FCF75DF8D44F8FBE1",
+ "d_a_obj_usaku.rel": "7E25495502A308B1AE93AF29F1E676D3D3115DB3",
+ "d_a_obj_gb.rel": "E9C45749EE3F0670241B9E4057925F5039EFFE48",
+ "d_a_b_gos.rel": "7BB6E4EE49FE98658332DE17D41F35FDC1DDB769",
+ "d_a_obj_nougu.rel": "D040E9A5084BABB2F016693E3BBDE2917CA10A7A",
+ "d_a_obj_roten.rel": "02798D540EBD5A181FB8B2CC5C566EB70F2332AF",
+ "d_a_npc_seib.rel": "436A1F6EC287BBC38D651C0506FD4921B9E3C0ED",
+ "d_a_b_dr.rel": "BF65B5203FBA5EECDD4A8A99027CB2892F81FC84",
+ "d_a_obj_bhbridge.rel": "4A307732FF244743B849B4A85F31BED099087506",
+ "d_a_obj_sekizo.rel": "2D3032A2B2EAE24FB1BB55434D5DFF8D16E594F3",
+ "d_a_npc_mk.rel": "11A3D707F3DFFF9C90C32FB02C899FF7EA18CFB7",
+ "d_a_obj_saidan.rel": "9BDF2158AF4DAFC1B5973EDD9D02A3D1E56031A7",
+ "d_a_obj_grz_rock.rel": "7FEF7D03425497EDE843C8620ACC94571B583A48",
+ "d_a_obj_kaisou.rel": "14A672D1ED97EF533BABD5ABA8F4BDA0508CC0C0",
+ "d_a_npc_lud.rel": "155B716F01B24223A8B8789B69140E1C7B65B616",
+ "d_a_obj_mvstair.rel": "888150C9C93720A4C78D7FD0014387A5FDBFCCA0",
+ "d_a_npc_drainsol.rel": "CF41932F85034FAF5211DD1F1059433E3E78AA2B",
+ "d_a_obj_pdoor.rel": "C70D750A8ADB798F4EA366861153335865985D8E",
+ "d_a_e_sf.rel": "4ED3AE61045ECABEB6E5BFFA7FB1D82CF2828962",
+ "d_a_e_tt.rel": "1588A8F5A0F63926B233DDD945D39B41CF9CD415",
+ "d_a_npc_ykw.rel": "76EE59945CA89E33ABFFE30FF7B41FCF973BBA21",
+ "d_a_e_ge.rel": "C02EC1BB061C65AEC8B71818CE44CEEB198B01E5",
+ "d_a_npc_shad.rel": "30E609A4BDD55F0F084397B122C970058B47E42D",
+ "d_a_obj_pdtile.rel": "E7496705B786B250D2509C16FDC968F9E63B0C69",
+ "d_a_obj_inobone.rel": "0889E4303069CD7914BA27D9483FA73E8C137954",
+ "d_a_obj_msima.rel": "AE1F53AFCA10DE654FFCF56C88F5B58C8F87E635",
+ "d_a_obj_smgdoor.rel": "1A8EF8F5A99F9D74FB71CF8D0A7DA0D9F69EE369",
+ "d_a_npc_soldiera.rel": "112C68604B095E768CDA9D4C4BEE1D60655B9BBB",
+ "d_a_obj_bemos.rel": "CBE8D6BE9FFC4F9C217A42777B2795267F18ED03",
+ "d_a_obj_sekizoa.rel": "518527CE198D6B6046217B1181624CFE3D3603B7",
+ "d_a_obj_chest.rel": "F69D071883106072EDBBFB6BC95B5AF17A7F8C43",
+ "d_a_npc_zrz.rel": "147373E64B382F21040F8BD6224979279BADFE34",
+ "d_a_e_arrow.rel": "BE5E249A07C390CC058DAD3D2C33198888F0FAB5",
+ "d_a_obj_snowefftag.rel": "810D1BAE5AD823D4171EB417985C0EACB23C5E22",
+ "d_a_e_yk.rel": "F38457EDBA02E29649A98F07F77C51F6BC60E5FE",
+ "d_a_obj_lv4sand.rel": "0D14D26489E8E92A18835E91F2EF766B9E6D7B65",
+ "d_a_e_nz.rel": "3A2D8CECF7682096E359747F3C38A178B0AD27F7",
+ "d_a_obj_flag.rel": "EAA0E09581908521D2DF5FB729497D10DB819F07",
+ "d_a_obj_maki.rel": "71310A1C38F91411F30420AE3EAF16DABEA99BE7",
+ "d_a_obj_ss_drink.rel": "5A3DF887CA5FF2C3E8487FD2771CC472A365E122",
+ "d_a_e_fb.rel": "B604D92E4CD05D621C25E7EB947318AB0C50F264",
+ "d_a_npc_tk.rel": "237647CE68026DB5A63D17C0A63163E11C5CA30B",
+ "d_a_obj_automata.rel": "6DFBC2A4D70F05058874B5F03BA26D66D68EA9D5",
+ "d_a_obj_geyser.rel": "2D4FAA86812F920A8A93734C98A8047254820911",
+ "d_a_obj_cblock.rel": "523C070B27D87FDD633B8CB8D30DC397AA5D1674",
+ "d_a_obj_tafence.rel": "EBBFD99842E03EAA06A1ED5A281EC2BA8299AA63",
+ "d_a_npc_soldierb.rel": "1E7E307865690D93DEC95354CB925EFF8740ACA7",
+ "d_a_npc_pouya.rel": "4C45EAEB8ED17B3E3AB6F7B44436AD03B6CE5134",
+ "d_a_npc_guard.rel": "37C52DAEACE14C08F31B75C1106784548CFDED32",
+ "d_a_e_warpappear.rel": "266328865EA58B7F9FA3D55D239C754783DB2E97",
+ "d_a_npc_yamis.rel": "5B2783D393F82695BA664C98CBF26B71912EFBBF",
+ "d_a_obj_mirror_6pole.rel": "0E01A3107E03EE7D34E15321B45684EF068129A0",
+ "d_a_obj_window.rel": "D8086AEAA55F231764B070BDBDE35F0B0012C09D",
+ "d_a_b_zant_mobile.rel": "02070889A16EDC5E9DDCADF222FD0BBEF7B57054",
+ "d_a_e_ot.rel": "E68DDF87AA7D40CA26A522EE7991A44C19A2459B",
+ "d_a_obj_swturn.rel": "0F2CBDB33705F8FA6F3D81D236B4EB9A9EC171C1",
+ "d_a_obj_rcircle.rel": "509FA8C42A2750198405386BD547C678666960B8",
+ "d_a_obj_iceblock.rel": "AE5B7BD9239E6ED21770B54B91A2D9CDF7C6C1DC",
+ "d_a_obj_lv6togetrap.rel": "4799FE0B07BCC77B683E768D78D85E9E37F3FE12",
+ "d_a_npc_sola.rel": "17A56AAC1E2E03CBF892152012B93EE48ED7D9E7",
+ "d_a_tag_stream.rel": "6E8DBB2698825C796E070492EC3AF92A99A0245F",
+ "d_a_obj_wflag.rel": "43D18BD91572169936926CF2E04591ACDAD7DEE2",
+ "d_a_obj_cdoor.rel": "A2B67AC2DC108A530573803B12C58E0642196B99",
+ "d_a_obj_lv8lift.rel": "0350D3E5E8B404B1ADCF53F1AB35CBA27C619580",
+ "d_a_obj_zcloth.rel": "0D8D92229CA1492035038756E869488B2BD4912A",
+ "d_a_obj_katatsumuri.rel": "641A1BDA45020122996F8CF6AF8B916DA57A728B",
+ "d_a_obj_togetrap.rel": "092509DC19CE6F5E86796A9CF06713E96DB46EF4",
+ "d_a_obj_web0.rel": "851645238A9651D100BB904AB6BBC74F3AB053BF",
+ "d_a_e_zm.rel": "B76D55F462CCFD693DD31993943E6D7F04B82DAE",
+ "d_a_obj_ganonwall.rel": "CEA1E292EB2671E0D449468228509C221FE238E9",
+ "d_a_obj_kantera.rel": "156FF8944FA1BEDB272543E350B88342224645E1",
+ "d_a_obj_gra_rock.rel": "6392A8AAC7C216104B398A3C32127FFA98204047",
+ "d_a_obj_lv5yiblltray.rel": "1F4986C0986FB71B02498E2DD88D8AD56689C601",
+ "d_a_obj_swlight.rel": "8EE15C9C80656AB121E7FC5BE889FDFF0C5D76DC",
+ "d_a_obj_tks.rel": "FCC49655429F928E00C4987CFDFEEC543802E284",
+ "d_a_tag_spinner.rel": "826FFF6A9493FBBDC6F48456CF7B72E509391B13",
+ "d_a_obj_nagaisu.rel": "490CC4E869384D3FE587D56E0DD364C87A7D5162",
+ "d_a_obj_stopper2.rel": "3E176DDD51A928DFDD2FD1298B39D65FECC696AB",
+ "d_a_npc_ks.rel": "D9A35B55C3A1307723AE59B0C915EDF8846187D3",
+ "d_a_skip_2d.rel": "A6940A39F5607E5774D3481E0882B54D5A6A3CAA",
+ "d_a_obj_grawall.rel": "E429791CC10CA6E653F10312DF57426DD4C5B395",
+ "d_a_obj_balloon.rel": "1D800CA650D91893BBE03EB30E10263CAD094B0D",
+ "d_a_obj_rgate.rel": "F2F730BC91E14C40BE006C8F2048E1FF7FF5915B",
+ "d_a_tag_schedule.rel": "6A3FFBE387423E5296B13A15620AEE90C676AB5A",
+ "d_a_e_sm.rel": "5F35F70A575EEB84B48CF23C77FD068F025FD15D",
+ "d_a_obj_waterpillar.rel": "5C320FBB9A2EA5394F0E3433ADC2F0FB4FB4D4FB",
+ "d_a_obj_swballb.rel": "DA34E2D03406DC539DF538F9709236FBFC9C034B",
+ "d_a_obj_fan.rel": "776A1A296EC976346DCD86FDC780D15BA68E4C30",
+ "d_a_obj_lv4edshutter.rel": "EC9171E061DC6D8F71AD422E43D14CE8820725A0",
+ "d_a_tag_setball.rel": "397A46BFA7597FA39EC9C4513994FF017FB5881A",
+ "d_a_obj_dmelevator.rel": "D5B4A6C9DBA147542CBB8829F31D9ABD3620BC48",
+ "d_a_kytag13.rel": "4D2C5A3E793B70A06E1BD8E2D42D654275E4C8CE",
+ "d_a_obj_onsentaru.rel": "FE5830E76B8F2748380CDC6F2A90E64EAE60C9DE",
+ "d_a_e_ymb.rel": "8FEBD644363DAFBE1B44102A05AE01793F9C7264",
+ "d_a_npc_seira2.rel": "FE12CF50B49CD20DD82052D3DA814C2F19152BF6",
+ "d_a_obj_wind_stone.rel": "3E89070602385F10DF61A11E2516ED1EF65DD8CC",
+ "d_a_obj_lv6togeroll.rel": "9A2D18F7FECFEDFCE2ACE774C36F672D09AC325B",
+ "d_a_door_boss.rel": "6896014400E237A57503F0D6075D38437805A6AE",
+ "d_a_e_gob.rel": "AF8B3C0660805834931F1F16289AA1A3FA43526D",
+ "d_a_obj_ita.rel": "53BB8ADEE6D73548DF36F47614591C292C65A501",
+ "d_a_obj_ndoor.rel": "263EF49FECD2B55209F3679C1806E1CC9DB53263",
+ "d_a_kytag03.rel": "10169AABCC557EA76F4A312D75AFE64570C2A1AC",
+ "d_a_npc_henna0.rel": "E8F4DA7381506C21180F3C52200952D551D5CD0D",
+ "d_a_npc_passer.rel": "756F697E528615E11E634F4DB01C5398B3BE2445",
+ "d_a_obj_kznkarm.rel": "3B0C06C022D6E379254E5656E57999E2B3D1FAC6",
+ "d_a_obj_zrturara.rel": "966A7403702256D36C9B8F402ECF78113625C204",
+ "d_a_obj_wood_statue.rel": "994ECF3B09ABB9E766CEE034D0C8F73BDBF19C30",
+ "d_a_obj_rstair.rel": "144B96211EB77BEE34CF1DAB46D593E64D78F723",
+ "d_a_kytag15.rel": "019DD0EEF225D4CDA826C311159EDFF4311EB14D",
+ "d_a_obj_lv6szgate.rel": "F067D7DF7959A9E64AC481F0D9FE254BE6EA8C39",
+ "d_a_tag_myna_light.rel": "EADC18ABE78FF09EC5AB417956817D7381657A85",
+ "d_a_e_oct_bg.rel": "53FD064584E563A847F16AD5BB219F2881D00AC1",
+ "d_a_obj_tp.rel": "60633D098B39F32ADF8D87B72A43816189D4FA66",
+ "d_a_obj_lv4gate.rel": "F20B986A600C4A6D8E716560ACD340FD7B2CEC63",
+ "d_a_tag_firewall.rel": "235994000D7B1C9F5FDAB89536A820D84451FB82",
+ "d_a_obj_master_sword.rel": "A9A8F90F7EBF6B79FB92613E3FB7650B669B958D",
+ "d_a_tag_lv6cstasw.rel": "F67B0243137AD5E5E14945F749C09513D747A872",
+ "d_a_npc_moi.rel": "5DEA61797F9E0D8BE21BADD2475646D861BE6E26",
+ "d_a_obj_myogan.rel": "2827303B423FF79D410188C9C1C352EC39E88CBF",
+ "d_a_obj_toby.rel": "D07A38632A2BB32B7BD3DC2B6B41364EC6384850",
+ "d_a_obj_lv5floorboard.rel": "B0C8E2B573C1DAB74CC499D63C1DB48BBEFD5099",
+ "d_a_e_yg.rel": "E45743E2B5D566996CCBE8F6722293A1ADD05779",
+ "d_a_obj_smallkey.rel": "3496EFF62F65196A34B5A987CC23A788243F63F0",
+ "d_a_tag_instruction.rel": "37E1C451FF986AC9286112A5B1026767FC3B5217",
+ "d_a_obj_rfhole.rel": "2478DBD7BAE7015C1F3AFBB16D66A9F72A1ECC05",
+ "d_a_peru.rel": "8CE019FD4C7543C03877F3ECA46E785F65673013",
+ "d_a_e_tk_ball.rel": "A73201E49420BE8BF799A0841CC16D852E3172F6",
+ "d_a_obj_tornado.rel": "B41F1B85C335FB3F949E7967BCD1353F9514151D",
+ "d_a_obj_mirror_table.rel": "DE75BEAA769F30E9C63BE98CB3D188AA49BFF5A1",
+ "d_a_npc_impal.rel": "42A9A59054E4373A5B9AFC2DF94C264C21C2AA4B",
+ "d_a_obj_zramark.rel": "36930FF381E3B738573CB78EA7DA192EC8E2293F",
+ "d_a_obj_ss_item.rel": "2C0F4BBB3E3BE695F2D7A4F61EC6FAB4C8A4C320",
+ "d_a_obj_laundry.rel": "538F91F01A9DB44A1C9F766C955AB0F9B4ABB205",
+ "d_a_npc_kyury.rel": "1425AA779D35E77C4BCCFEB1CA4EE4B63F112B61",
+ "d_a_tag_mwait.rel": "3D41EC3404F61E26C9C989091DBFFDC11165F1E5",
+ "d_a_obj_lv4chandelier.rel": "3A19942FB9EC3E3D2E4B322D5C28A3E6678274C8",
+ "d_a_npc_saru.rel": "60106E049B39378F1F2610FD5DC107F121E5DDE6",
+ "d_a_mant.rel": "6B084ACB24FDC4606A6F14FD8F785777A521D43C",
+ "d_a_obj_lv5key.rel": "346D99F5C90219C815CA8C48D751708A7FB1DC44",
+ "d_a_e_bee.rel": "53A6FE4CE1AFF34BA085398433CDC04EAA4DC7B8",
+ "d_a_obj_zdoor.rel": "B527717E6D2B2BE7C7D60538784047A22ADA97AB",
+ "d_a_e_ym_tag.rel": "809C1B9AF5E970E1DC980D3577D7D9566549D58F",
+ "d_a_npc_shaman.rel": "5583E060CBED834E3CD50D57612400E05B5BD499",
+ "d_a_cstatue.rel": "0B377F95F939409266D799F5D32237CDFB1EE9F5",
+ "d_a_obj_kage.rel": "6440097C035AF011A8E47E32D69532B649600E23",
+ "d_a_b_oh2.rel": "3E0180EDA3F540633181ACE9B1A2CC8BD533475A",
+ "d_a_obj_maglift.rel": "B1561F39FBFC1AFE1684BD3C046A7B402FD77851",
+ "d_a_swtime.rel": "F4A4638E454C817197B783395B481B3D113F7626",
+ "d_a_obj_timefire.rel": "E1244D6275911E019E58FAAE68C604916DE0AFF8",
+ "d_a_npc_cdn3.rel": "C27BA6D8A563E1FA30B279E3B4E966104055DE5A",
+ "d_a_obj_mie.rel": "33DC90BD91AA7D820081F33EA92E8D0C665A9C67",
+ "d_a_e_gb.rel": "F21CE131A2C435E63AB32D2C65404616F49ECE1A",
+ "d_a_obj_octhashi.rel": "53B8C868712E9FC730A390E6ECE51091A8916FAA",
+ "d_a_e_mk.rel": "EC0E8AABA4F0D65981EE736335727F199F5219C3",
+ "d_a_obj_onsenfire.rel": "E229A026DEA509C95996F0245357DCF90A2FBCAD",
+ "d_a_obj_cb.rel": "4704E2FC0C1C9A0E46E3673BA42F6F8BC46EF629",
+ "d_a_b_bq.rel": "9ED97B0F5D6F736312051E08B8B5182F83615831",
+ "d_a_obj_potbox.rel": "02C9E127F1F8C4752D8FEC40CB96644B2353A63A",
+ "d_a_obj_lv4prelvtr.rel": "7129A7C076ADE058CC69129532A008DCB112940F",
+ "d_a_obj_ihasi.rel": "B29DAA777A035C9036EE841C9BAC10F867A655FD",
+ "d_a_obj_pdwall.rel": "555AC0D10D428594900A05D42B3955158EBDC17F",
+ "d_a_tag_chgrestart.rel": "F7748A94BC46E1134EAD99AA9C628D11A5D70435",
+ "d_a_npc_gnd.rel": "C24208BE125CB9CF58FA4A11361F1041564393CF",
+ "d_a_npc_myna2.rel": "99D814708876522A98E45FD36CF0E2D621A70EF4",
+ "d_a_obj_web1.rel": "8F205B1F60E93E77E2C6B87035BAC47B26C66705",
+ "d_a_npc_kasi_hana.rel": "D149D5E56ED3ADB781611D4FA02847973EA3DDBA",
+ "d_a_obj_hfuta.rel": "81A3335E375BB103C9418E9B8809405992363ABF",
+ "d_a_e_cr.rel": "7CD9B7257E5819186CAEFC4D703FAC9B94C3BEBD",
+ "d_a_obj_wood_pendulum.rel": "B81DBBD207AEA5E3F6214E01863BBAE40C75729E",
+ "d_a_obj_onsen.rel": "7059C9E19FF0223ADBBDCFE2045256489A920B43",
+ "d_a_tag_lv5soup.rel": "74144FFBC97B17313713CE6A9062ECF7FF8865F9",
+ "d_a_obj_hasu2.rel": "963ECF522BB4B40A21154B6A740C6160F5EEFC68",
+ "d_a_obj_catdoor.rel": "E4F7A1306FD61D8DD58CE1417429393EC4050F21",
+ "d_a_b_zant_sima.rel": "A0756117065438AE9582ED1AD8FE515A71ADCE7D",
+ "d_a_tag_guard.rel": "7D08FB3103A415B7E7D55D8DE68D92E8E73FE121",
+ "d_a_obj_ganonwall2.rel": "0B2AE3E122627D9D446CB7342ADEB76C90F83BF2",
+ "d_a_npc_clerkb.rel": "31E76C0CBEF53DAA87F3DFBAB586FDA93F18E7EF",
+ "d_a_obj_hakai_brl.rel": "91FD4F4546A0457EC53AD1B607C1B4FDFBDF68F4",
+ "d_a_b_gm.rel": "14FBD86A93CE66E50E5A9597A68B76478B79D653",
+ "d_a_kytag06.rel": "F9AC933DFAEF59F2D5917D959D2C542DE3BE96B9",
+ "d_a_l7demo_dr.rel": "5D232DC81A02B2A0C11CB50F51B4043DFFE82C28",
+ "d_a_e_yd_leaf.rel": "07FB1D63AC146CFF82FD742CDA2F44A314B3C0B8",
+ "d_a_npc_zelda.rel": "ED2B61B282862E431CAD32035695B37B349A97C1",
+ "d_a_obj_lp.rel": "D275504F15B5084685186FF7B1C9AFE0DF4EB865",
+ "d_a_tag_assistance.rel": "5DEB5EC63E7C78A1002E4D686799D03E1EEE0098",
+ "d_a_e_bu.rel": "83FB29310F070C1E611D99E71AFCC0A60849CDCA",
+ "d_a_obj_gadget.rel": "0C3A6FFD32FDF49F01BEA15DCED0536FF32B2B1F",
+ "d_a_obj_tornado2.rel": "A6CC3C7E7CBB951E7ACFBF0EAA0D76AED304DBF6",
+ "d_a_obj_amishutter.rel": "8E6B7BC6E094DEBB430607CD9364C502AB962800",
+ "d_a_obj_lv6egate.rel": "5F059A8677CCDC9784114F5179426ADB9B01380C",
+ "d_a_obj_snow_soup.rel": "CF9F688D53A37F3149735250AD2CDE9A3DC53000",
+ "d_a_obj_kabuto.rel": "093D65B63EB8CF2F1A7FCB68105928B316AF3DBD",
+ "d_a_tag_wara_howl.rel": "12E2F139FC246511574F8B33207BA3E93549E705",
+ "d_a_e_bg.rel": "590C37419BBB29304D635B2B43AECD4A5D20C306",
+ "d_a_obj_treesh.rel": "2B7C1E7C0ACC7DDFE8531380A195CAD4163D1BAF",
+ "d_a_obj_cowdoor.rel": "7563F37577AC0FFF3011FB586F6B2BF0AF7442F1",
+ "d_a_npc_seic.rel": "7B76269687ECF5C88290C547194CD631463247EA",
+ "d_a_bullet.rel": "C89BBFD16A14172EB4900D50909583FA926D8CC0",
+ "d_a_alldie.rel": "F57F4BA508831D57D294ACDCF5AF744D6B6386EB",
+ "d_a_andsw2.rel": "F895E1B7F4E7189148A0A81F6232CDEFA610895D",
+ "d_a_bd.rel": "E7F945B9066E1C567059BB70ECC24507C70BDBA4",
+ "d_a_canoe.rel": "FBF7AF2E0C8D8BF1AD39DF61B77C4C96E7A1E6BE",
+ "d_a_cstaf.rel": "8B3F15D8D4E83D1952FFB8E37D8D4541D21EF4F0",
+ "d_a_demo_item.rel": "98CF296F2267B40661B0AE182F3B5927A41DB730",
+ "d_a_door_bossl1.rel": "4DECE1E86A76CB79B070F5F7FE7CE4F09736B118",
+ "d_a_econt.rel": "9D13338E4D6CB15B8D14063A8D0953915B21C2C2",
+ "d_a_e_dn.rel": "D82300161F5B7CDAC86E2034E6B93E3B825B463A",
+ "d_a_e_fm.rel": "2F30656BE63EF0FB9F9F120C26408894735E5822",
+ "d_a_e_ga.rel": "196EA1D1231284C9CA2826F94327716B913878DA",
+ "d_a_e_hb.rel": "2AEF8C9FD72680F1FBA24B1B244F27904D05400F",
+ "d_a_e_nest.rel": "20526E90B19E8EB4885040AFD5150C85A07CA2EF",
+ "d_a_e_rd.rel": "74A4AD073BD7E5F10AA48948564C1998DDC21900",
+ "d_a_fr.rel": "9670D0609FA86186E9F3919A87B04A7E3ADC57E5",
+ "d_a_grass.rel": "4A64BF1F6BE52595FFD085371386B63B7B2D636D",
+ "d_a_kytag05.rel": "AF22BA95C34DEA2EDDD19BC6490355B7B6AEE8BA",
+ "d_a_kytag10.rel": "25552DAEED9AFE4F5B51DC9F126BD04AB871C31A",
+ "d_a_kytag11.rel": "D08699B92E1241046C029E5FEC64446891D31B71",
+ "d_a_kytag14.rel": "F3A09933A762EF775E222F4EDFDE63F00D82713A",
+ "d_a_mg_fish.rel": "8BA390AFB3F5E111C9C1BF78D16EAD811D483EB1",
+ "d_a_npc_besu.rel": "B5B650AD73C766B7DFB47CB19C09E5D2B4E20BC1",
+ "d_a_npc_fairy_seirei.rel": "A9F4047C059ACB8D49DB0BCAD560300036E123AF",
+ "d_a_npc_fish.rel": "84350CE66A9FE9C84C8420E634F45C4BE37E6532",
+ "d_a_npc_henna.rel": "9DD1519F71BCA092777AD6DBBC35A7CCB35EFD6D",
+ "d_a_npc_kakashi.rel": "C6FE7B73ADF7E47515C12B3155F26D3E5FF8A683",
+ "d_a_npc_kkri.rel": "F27A8FAD2548CE09856E026A5366B2DE33E87A9D",
+ "d_a_npc_kolin.rel": "00FACD192A3F4CF3F991E20FED5E8B146EECE9FE",
+ "d_a_npc_maro.rel": "B1D4A7844EFAF46AA53E0321E769938D927D7F0A",
+ "d_a_npc_taro.rel": "D019C7F68901B440E03DEE14C7BDBE3EFBF5C13D",
+ "d_a_npc_tkj.rel": "AE82B1533B583C7502A615B8774FE1D42DBA94DF",
+ "d_a_obj_bhashi.rel": "72829A066D01AC610BA0FD2A123E4B5DCF965B0A",
+ "d_a_obj_bkdoor.rel": "7BF44AFDC2520B9BD2C11390F6D4B718C2409FA4",
+ "d_a_obj_bosswarp.rel": "80D05BB5762483663D6285DC26114BC36DD5FF6C",
+ "d_a_obj_cboard.rel": "2C84A642E38F1BF18D75D92161962F88EDF01B2B",
+ "d_a_obj_digplace.rel": "151E3325E286EC3D16ED16015AB46D15E846234A",
+ "d_a_obj_eff.rel": "8D3F81AB4A782BE56E217B465DABA0086CE8E18A",
+ "d_a_obj_fmobj.rel": "8251C7AF3757B603AC243AA835EE2CEC65C74555",
+ "d_a_obj_gptaru.rel": "AFF58B815666012D66097C72FE605CBC2B608A38",
+ "d_a_obj_hhashi.rel": "9E340CE4E9859A8C60D11C76B4E8BBA4D1084320",
+ "d_a_obj_kanban2.rel": "D1299ACB5C6FFF654BA3329DF83A61C33963228D",
+ "d_a_obj_kbacket.rel": "2628BA945C1C7DE6B28C1F2DD54CA632EEB6272F",
+ "d_a_obj_kgate.rel": "998F16F7E74E0E2CA9360A7649F10EAD22B61CE6",
+ "d_a_obj_klift00.rel": "A96EEDF6CE28FB62132F59738EEB08D82DADDBD2",
+ "d_a_obj_ktonfire.rel": "746B9EBC1E51D0F9967FDE234EC6B6C86F1643AE",
+ "d_a_obj_ladder.rel": "7FB6D15388CA0B877EDEDCF6A0E6371A6A62281E",
+ "d_a_obj_lv2candle.rel": "B61251F4D649C72149106037C93A4A0E9A00E5DC",
+ "d_a_obj_magne_arm.rel": "B82556B6EDADB0B0ADB2A54D76BDF4F87DA7BE93",
+ "d_a_obj_metalbox.rel": "B0882A27C16895FEAC4F971A248481060CA7CB49",
+ "d_a_obj_mgate.rel": "6A06F1A12C13141E5F76A0AEB388F0FE755CB325",
+ "d_a_obj_nameplate.rel": "7DD5B666BC2EB95C202E7B6CF1773506414877D3",
+ "d_a_obj_ornament_cloth.rel": "0488B6B2E72F0923E32C216EDB5F02F4679A9B24",
+ "d_a_obj_rope_bridge.rel": "3AC54A1038E14D5B88AC8FF455479066A595B652",
+ "d_a_obj_stick.rel": "08B49C8B7913033CEB4F78D614580BD0544BF465",
+ "d_a_obj_stonemark.rel": "E7573BCB69F6E6E592C5F66006E75EE946C4546F",
+ "d_a_obj_swallshutter.rel": "497EEBE388585628C193A7C2EB1028904C32255A",
+ "d_a_obj_swpropeller.rel": "4DB0C1D63563C6D4BDC8581D243ABB9F4B9E577F",
+ "d_a_obj_swpush5.rel": "5746C81A57908E5875C23ED0EB678A0A7BDA4DC7",
+ "d_a_obj_yobikusa.rel": "E12DF50F9A1E080519D77394E7D55F9947954F67",
+ "d_a_scene_exit2.rel": "91CDED403A267136DB3BE06B71F8EC6A6627EFEB",
+ "d_a_shop_item.rel": "351AA76F7FEA43EBE475C6AED13A3744E2B1DCC2",
+ "d_a_sq.rel": "F455FA9E5218ED6473AF44794D80683B75B0E628",
+ "d_a_swc00.rel": "99841E0066F575461577984E0E1B9362981D1993",
+ "d_a_tag_ajnot.rel": "4B549E2F1B960613C7456A72D6A4D5B684CDD374",
+ "d_a_tag_attack_item.rel": "9C75794FF198CBFBFD5F8157D6C19B934DC18CE4",
+ "d_a_tag_cstasw.rel": "ED62D3D2A85402CC0812FFF6B2FDBCFB62FD8697",
+ "d_a_tag_gstart.rel": "4D2085BD61830C6B682F00AFCB9F7302D7E6E141",
+ "d_a_tag_hinit.rel": "5CBF16B05F91955420C84B8B0E15ACDCC79E5B9D",
+ "d_a_tag_hjump.rel": "6CAE474C5861E6CBE99420BC2685A46D7AA4706D",
+ "d_a_tag_hstop.rel": "479771BCFC8CA4E45BA00B69FCB682D008F706D0",
+ "d_a_tag_lv2prchk.rel": "E7D3691B60FBE23E4BD90F6B516545280B1C5765",
+ "d_a_tag_magne.rel": "37E6A11654A16414842C9ED88203A360AA64DF50",
+ "d_a_tag_mhint.rel": "18F7E5B78A5CC6EACF5DECAA234A46E6ACEE6F33",
+ "d_a_tag_mstop.rel": "C5460318101CEFAD5D569DFA48CBB37967C1A505",
+ "d_a_tag_spring.rel": "F34CDEC843A7B6A7CEC9A88FBD56A92D070C88A7",
+ "d_a_tag_statue_evt.rel": "DF2BC445FE488E6E214CEC2E0E4755CE72ADA731",
+ "d_a_ykgr.rel": "A4AA87DED1C0DBE36ECBD0D60B6E94707738C52C",
+ "d_a_andsw.rel": "50D929E32EDC2E2FA9E5DD2604EC071A886AD9D2",
+ "d_a_arrow.rel": "F79AB4E4D39BAAA805B519857C20F524E4ED542F",
+ "d_a_bg.rel": "7A8F1221221370231107D4A7520D9315463B42A3",
+ "d_a_bg_obj.rel": "27BD6A4E89386C4A9C5FF63EBA325D842F41FC77",
+ "d_a_boomerang.rel": "A5F6504CF570BF710623139F1C54823D1E570958",
+ "d_a_crod.rel": "C1E0596058798DC0E8AE28B9055BD18F37E9D6C5",
+ "d_a_demo00.rel": "32FEEBA3AF4F29B7B2E935FD4E44714DB14B2BF1",
+ "d_a_disappear.rel": "3E23E59E83CDCA79B93ECFD92B7DA77E8F14DD59",
+ "d_a_dmidna.rel": "ACEB9F8C9B8F916BF138F2C111DDD66322646239",
+ "d_a_door_dbdoor00.rel": "C257DC9C44C911BCE0B4BE6BE6E327A689B84E36",
+ "d_a_door_knob00.rel": "7182095533A3E3C79D05FADCEF59428E60DB6858",
+ "d_a_door_shutter.rel": "8D3A8253E22DD82390F02D0DA5A5C50297A28961",
+ "d_a_door_spiral.rel": "0A74583715439DA08033CF027D40556FED4FA0AB",
+ "d_a_dshutter.rel": "BD35F93D9EAFA67BFFADB2E3A6F034AD2EC7FCED",
+ "d_a_ep.rel": "9492F07CB16BA6CDC2868011E9576B66E1B8A666",
+ "d_a_hitobj.rel": "EB34BEA97A72F6A359425E80F348A029D0AB4E1E",
+ "d_a_kytag00.rel": "A4FA22E24367C5AE2713DA6D0CE87B116B7C74C3",
+ "d_a_kytag04.rel": "9184C991C7E1033D32D2B552EA60BA37B6B13435",
+ "d_a_kytag17.rel": "5D4EB463AFD7A42B12D1CFF0629BBFDCC9948642",
+ "d_a_mg_rod.rel": "EF63CE3D8770824281B8CAC92D50D5E289F12B3C",
+ "d_a_midna.rel": "4BB5675D5889B207024BEAE5EF902C079A484886",
+ "d_a_nbomb.rel": "EF7B403162E4C226E273CF83EE3CEDD90B283E50",
+ "d_a_obj_brakeeff.rel": "2EE1F6209C3E479747BF0248EAE3E1F2B7E02519",
+ "d_a_obj_burnbox.rel": "7360E28438CB6949CA66B9CC470ED4291A92049B",
+ "d_a_obj_carry.rel": "CFF5DAD263B7427995495244FABF8C6B20963BD9",
+ "d_a_obj_ito.rel": "C75C9B3850184F160C827F9EC82944A5B140BB3C",
+ "d_a_obj_life_container.rel": "00B2413D70AA513E3CAC6EAEE331974E78929DAF",
+ "d_a_obj_movebox.rel": "D03C290D4E0432CB2901C60443D581719BCDF740",
+ "d_a_obj_swpush.rel": "3D25B99284F26213877F7886A94F97BDD5A71C47",
+ "d_a_obj_timer.rel": "ABEDEB86ECD5B885BDA3BBD606C5392B24629664",
+ "d_a_obj_yousei.rel": "BFCACC1270019585ED27DDE2A99A10A0DDFF82FB",
+ "d_a_path_line.rel": "2CB0EC84A659B065B73E1DB56F33641458815674",
+ "d_a_scene_exit.rel": "442047A1B1DCF8FA0320626F5706FBD0E03A4141",
+ "d_a_set_bgobj.rel": "370794A3A4B9B6586EEE932F425EDC1A3C27ADB3",
+ "d_a_spinner.rel": "65A3571E8F30A1C73C7BCA667631EE32F1579ED7",
+ "d_a_suspend.rel": "C116F31CBC1B56075521AE7C3F5F1BB5BC276F46",
+ "d_a_swhit0.rel": "00EF39125BA3F88D3BCA3643C00DC9859F166362",
+ "d_a_tag_allmato.rel": "F1668F7FE8BDCD92BC3C31F271F71437ED07BAA1",
+ "d_a_tag_attention.rel": "B95A733AD9190EEF65CF7C7BDFAE4AD423BD5436",
+ "d_a_tag_camera.rel": "61A139C1231240D1B6DAC0B573BC0BC492FC6B7B",
+ "d_a_tag_chkpoint.rel": "28C0BD2D679B0422321E73F4057492A92F04EDF1",
+ "d_a_tag_event.rel": "21CB6BC47D366B55DF7E9858A2586206F24FC429",
+ "d_a_tag_evt.rel": "1D189EB69E8BCF0A752E207468A5D19C87B083AB",
+ "d_a_tag_evtarea.rel": "0256E4997A96FFA86084AD893DB6FEBE3BA4824D",
+ "d_a_tag_evtmsg.rel": "646236AC798E86DAF6E7056E85AC3C62AA55967A",
+ "d_a_tag_howl.rel": "48EFFA4E2F24685E91515E8136352E409BBEBCEB",
+ "d_a_tag_kmsg.rel": "0DCE73A8A36D6CEF7158F27375CC690376C8564A",
+ "d_a_tag_lantern.rel": "2909F61733F38AD5EF747D5F5AF983A9FB722FEA",
+ "d_a_tag_mist.rel": "066C8ED7B858891135F32B708EDABBB61ADE7A83",
+ "d_a_tag_msg.rel": "611904B4A4B1B56C90A6853643703519EF2F971E",
+ "d_a_tag_push.rel": "1A434D9F41CEABA4C4571CE0D71EAF561A7EC88B",
+ "d_a_tag_telop.rel": "95DC9717C4B5E9BAAC77CBFC4BF469F1278350C0",
+ "d_a_tbox.rel": "CB03A08DB61259655228C32FA2B5CD3615B1A8D7",
+ "d_a_tbox2.rel": "0FF633A61E326CD44E979274438E6D9F3A9599F0",
+ "d_a_vrbox.rel": "04638B2DE5952BA61B55A6999A754880FD2DE145",
+ "d_a_vrbox2.rel": "2EF40542B8BAA3E0F0A833B577A8AB7376DC16B4",
+ "f_pc_profile_lst.rel": "FAB4CECCC4E49951B900C2BA8B5FE5F878E4ECC0",
+}
+
+REL_TEMP_LOCATION = {
+ "f_pc_profile_lst.rel": 0x80456BE0,
+ "d_a_andsw.rel": 0x80457900,
+ "d_a_bg.rel": 0x80457B80,
+ "d_a_bg_obj.rel": 0x804595E0,
+ "d_a_dmidna.rel": 0x8045CE60,
+ "d_a_door_dbdoor00.rel": 0x8045D300,
+ "d_a_door_knob00.rel": 0x8045E7E0,
+ "d_a_door_shutter.rel": 0x80460AC0,
+ "d_a_door_spiral.rel": 0x80467360,
+ "d_a_dshutter.rel": 0x80467420,
+ "d_a_ep.rel": 0x80468180,
+ "d_a_hitobj.rel": 0x8046B2E0,
+ "d_a_kytag00.rel": 0x8046B6A0,
+ "d_a_kytag04.rel": 0x8046CAE0,
+ "d_a_kytag17.rel": 0x8046DAE0,
+ "d_a_obj_brakeeff.rel": 0x8046DC40,
+ "d_a_obj_burnbox.rel": 0x8046E620,
+ "d_a_obj_carry.rel": 0x8046EF80,
+ "d_a_obj_ito.rel": 0x8047B200,
+ "d_a_obj_movebox.rel": 0x8047DA00,
+ "d_a_obj_swpush.rel": 0x80482C60,
+ "d_a_obj_timer.rel": 0x80485120,
+ "d_a_path_line.rel": 0x80485700,
+ "d_a_scene_exit.rel": 0x804857C0,
+ "d_a_set_bgobj.rel": 0x80485D00,
+ "d_a_swhit0.rel": 0x80485F80,
+ "d_a_tag_allmato.rel": 0x804874C0,
+ "d_a_tag_camera.rel": 0x80489A20,
+ "d_a_tag_chkpoint.rel": 0x8048A680,
+ "d_a_tag_event.rel": 0x8048ACC0,
+ "d_a_tag_evt.rel": 0x8048B8A0,
+ "d_a_tag_evtarea.rel": 0x8048C480,
+ "d_a_tag_evtmsg.rel": 0x8048CEC0,
+ "d_a_tag_howl.rel": 0x8048D8E0,
+ "d_a_tag_kmsg.rel": 0x8048DE00,
+ "d_a_tag_lantern.rel": 0x8048EBC0,
+ "d_a_tag_mist.rel": 0x8048F1E0,
+ "d_a_tag_msg.rel": 0x8048F760,
+ "d_a_tag_push.rel": 0x80490240,
+ "d_a_tag_telop.rel": 0x804909E0,
+ "d_a_tbox.rel": 0x80490C40,
+ "d_a_tbox2.rel": 0x804969A0,
+ "d_a_vrbox.rel": 0x804984A0,
+ "d_a_vrbox2.rel": 0x80498A00,
+ "d_a_arrow.rel": 0x80499B80,
+ "d_a_boomerang.rel": 0x8049E040,
+ "d_a_crod.rel": 0x804A2DC0,
+ "d_a_demo00.rel": 0x804A4220,
+ "d_a_disappear.rel": 0x804A8EA0,
+ "d_a_mg_rod.rel": 0x804A9500,
+ "d_a_midna.rel": 0x804BC1A0,
+ "d_a_nbomb.rel": 0x804C6CE0,
+ "d_a_obj_life_container.rel": 0x804CC760,
+ "d_a_obj_yousei.rel": 0x804CE6C0,
+ "d_a_spinner.rel": 0x804D18A0,
+ "d_a_suspend.rel": 0x804D50A0,
+ "d_a_tag_attention.rel": 0x804D52A0,
+ "d_a_alldie.rel": 0x804D57A0,
+ "d_a_andsw2.rel": 0x804D5D80,
+ "d_a_bd.rel": 0x804D6B60,
+ "d_a_canoe.rel": 0x804DA460,
+ "d_a_cstaf.rel": 0x804DD8E0,
+ "d_a_demo_item.rel": 0x804DFAE0,
+ "d_a_door_bossl1.rel": 0x804E1D20,
+ "d_a_e_dn.rel": 0x804E50C0,
+ "d_a_e_fm.rel": 0x804EF000,
+ "d_a_e_ga.rel": 0x804FB000,
+ "d_a_e_hb.rel": 0x804FBC80,
+ "d_a_e_nest.rel": 0x80500EA0,
+ "d_a_e_rd.rel": 0x80504980,
+ "d_a_econt.rel": 0x805194A0,
+ "d_a_fr.rel": 0x80519800,
+ "d_a_grass.rel": 0x8051BC60,
+ "d_a_kytag05.rel": 0x805289E0,
+ "d_a_kytag10.rel": 0x80528B80,
+ "d_a_kytag11.rel": 0x805294A0,
+ "d_a_kytag14.rel": 0x80529920,
+ "d_a_mg_fish.rel": 0x80529C40,
+ "d_a_npc_besu.rel": 0x80536D60,
+ "d_a_npc_fairy_seirei.rel": 0x8053FE80,
+ "d_a_npc_fish.rel": 0x80542100,
+ "d_a_npc_henna.rel": 0x80542E20,
+ "d_a_npc_kakashi.rel": 0x8054B280,
+ "d_a_npc_kkri.rel": 0x8054F2C0,
+ "d_a_npc_kolin.rel": 0x80553F00,
+ "d_a_npc_maro.rel": 0x8055B4A0,
+ "d_a_npc_taro.rel": 0x80565DA0,
+ "d_a_npc_tkj.rel": 0x80573640,
+ "d_a_obj_bhashi.rel": 0x805769E0,
+ "d_a_obj_bkdoor.rel": 0x80578CA0,
+ "d_a_obj_bosswarp.rel": 0x805795C0,
+ "d_a_obj_cboard.rel": 0x8057B8E0,
+ "d_a_obj_digplace.rel": 0x8057BF20,
+ "d_a_obj_eff.rel": 0x8057C960,
+ "d_a_obj_fmobj.rel": 0x8057CB60,
+ "d_a_obj_gptaru.rel": 0x8057CFE0,
+ "d_a_obj_hhashi.rel": 0x8057F940,
+ "d_a_obj_kanban2.rel": 0x80581680,
+ "d_a_obj_kbacket.rel": 0x80585D60,
+ "d_a_obj_kgate.rel": 0x80588000,
+ "d_a_obj_klift00.rel": 0x8058AEC0,
+ "d_a_obj_ktonfire.rel": 0x8058C520,
+ "d_a_obj_ladder.rel": 0x8058D0E0,
+ "d_a_obj_lv2candle.rel": 0x8058DF60,
+ "d_a_obj_magne_arm.rel": 0x8058F2E0,
+ "d_a_obj_metalbox.rel": 0x80592E20,
+ "d_a_obj_mgate.rel": 0x80593540,
+ "d_a_obj_nameplate.rel": 0x80594020,
+ "d_a_obj_ornament_cloth.rel": 0x80594FC0,
+ "d_a_obj_rope_bridge.rel": 0x80595DC0,
+ "d_a_obj_swallshutter.rel": 0x80598100,
+ "d_a_obj_stick.rel": 0x80599140,
+ "d_a_obj_stonemark.rel": 0x80599FA0,
+ "d_a_obj_swpropeller.rel": 0x8059A4A0,
+ "d_a_obj_swpush5.rel": 0x8059B400,
+ "d_a_obj_yobikusa.rel": 0x8059C980,
+ "d_a_scene_exit2.rel": 0x8059E0E0,
+ "d_a_shop_item.rel": 0x8059E940,
+ "d_a_sq.rel": 0x8059F580,
+ "d_a_swc00.rel": 0x805A1380,
+ "d_a_tag_cstasw.rel": 0x805A1F40,
+ "d_a_tag_ajnot.rel": 0x805A25E0,
+ "d_a_tag_attack_item.rel": 0x805A28E0,
+ "d_a_tag_gstart.rel": 0x805A3400,
+ "d_a_tag_hinit.rel": 0x805A36E0,
+ "d_a_tag_hjump.rel": 0x805A3A40,
+ "d_a_tag_hstop.rel": 0x805A4300,
+ "d_a_tag_lv2prchk.rel": 0x805A4BE0,
+ "d_a_tag_magne.rel": 0x805A5420,
+ "d_a_tag_mhint.rel": 0x805A5640,
+ "d_a_tag_mstop.rel": 0x805A60C0,
+ "d_a_tag_spring.rel": 0x805A69E0,
+ "d_a_tag_statue_evt.rel": 0x805A6EE0,
+ "d_a_ykgr.rel": 0x805A83A0,
+ "d_a_L7demo_dr.rel": 0x805A91C0,
+ "d_a_L7low_dr.rel": 0x805AA580,
+ "d_a_L7op_demo_dr.rel": 0x805AB020,
+ "d_a_b_bh.rel": 0x805AE180,
+ "d_a_b_bq.rel": 0x805B3480,
+ "d_a_b_dr.rel": 0x805BAD80,
+ "d_a_b_dre.rel": 0x805C7A40,
+ "d_a_b_ds.rel": 0x805CB140,
+ "d_a_b_gg.rel": 0x805DE320,
+ "d_a_b_gm.rel": 0x805ED860,
+ "d_a_b_gnd.rel": 0x805F4960,
+ "d_a_b_go.rel": 0x806030C0,
+ "d_a_b_gos.rel": 0x80604180,
+ "d_a_b_mgn.rel": 0x80605640,
+ "d_a_b_ob.rel": 0x80610480,
+ "d_a_b_oh.rel": 0x8061B640,
+ "d_a_b_oh2.rel": 0x8061DC40,
+ "d_a_b_tn.rel": 0x8061EB00,
+ "d_a_b_yo.rel": 0x8062F380,
+ "d_a_b_yo_ice.rel": 0x8063A060,
+ "d_a_b_zant.rel": 0x8063E020,
+ "d_a_b_zant_magic.rel": 0x8064F700,
+ "d_a_b_zant_mobile.rel": 0x806506A0,
+ "d_a_b_zant_sima.rel": 0x80652960,
+ "d_a_balloon_2D.rel": 0x806533E0,
+ "d_a_bullet.rel": 0x80655860,
+ "d_a_coach_2D.rel": 0x806568E0,
+ "d_a_coach_fire.rel": 0x80657B00,
+ "d_a_cow.rel": 0x806584E0,
+ "d_a_cstatue.rel": 0x806635C0,
+ "d_a_do.rel": 0x80667C60,
+ "d_a_door_boss.rel": 0x8066F3A0,
+ "d_a_door_bossL5.rel": 0x80670BC0,
+ "d_a_door_mbossL1.rel": 0x806727C0,
+ "d_a_door_push.rel": 0x80677D40,
+ "d_a_e_ai.rel": 0x80679080,
+ "d_a_e_arrow.rel": 0x8067C6E0,
+ "d_a_e_ba.rel": 0x8067EC00,
+ "d_a_e_bee.rel": 0x80682780,
+ "d_a_e_bg.rel": 0x80685720,
+ "d_a_e_bi.rel": 0x8068A500,
+ "d_a_e_bi_leaf.rel": 0x8068DBE0,
+ "d_a_e_bs.rel": 0x8068E040,
+ "d_a_e_bu.rel": 0x806910C0,
+ "d_a_e_bug.rel": 0x80694AA0,
+ "d_a_e_cr.rel": 0x80697F20,
+ "d_a_e_cr_egg.rel": 0x80699EC0,
+ "d_a_e_db.rel": 0x8069AA00,
+ "d_a_e_db_leaf.rel": 0x806A1DC0,
+ "d_a_e_dd.rel": 0x806A2180,
+ "d_a_e_df.rel": 0x806A7600,
+ "d_a_e_dk.rel": 0x806AA100,
+ "d_a_e_dt.rel": 0x806AD820,
+ "d_a_e_fb.rel": 0x806B63C0,
+ "d_a_e_fk.rel": 0x806B92A0,
+ "d_a_e_fs.rel": 0x806BB9E0,
+ "d_a_e_fz.rel": 0x806BE860,
+ "d_a_e_gb.rel": 0x806C1C00,
+ "d_a_e_ge.rel": 0x806C7960,
+ "d_a_e_gi.rel": 0x806CD3A0,
+ "d_a_e_gm.rel": 0x806D1180,
+ "d_a_e_gob.rel": 0x806D7FA0,
+ "d_a_e_gs.rel": 0x806DF380,
+ "d_a_e_hb_leaf.rel": 0x806DFEE0,
+ "d_a_e_hm.rel": 0x806E0300,
+ "d_a_e_hp.rel": 0x806E5D00,
+ "d_a_e_hz.rel": 0x806EA520,
+ "d_a_e_hzelda.rel": 0x806F0C60,
+ "d_a_e_is.rel": 0x806F5960,
+ "d_a_e_kg.rel": 0x806F7E00,
+ "d_a_e_kk.rel": 0x806FA620,
+ "d_a_e_kr.rel": 0x806FF8C0,
+ "d_a_e_mb.rel": 0x80706080,
+ "d_a_e_md.rel": 0x80708D80,
+ "d_a_e_mf.rel": 0x8070A620,
+ "d_a_e_mk.rel": 0x80714040,
+ "d_a_e_mk_bo.rel": 0x8071CC00,
+ "d_a_e_mm.rel": 0x8071F8E0,
+ "d_a_e_mm_mt.rel": 0x80722F00,
+ "d_a_e_ms.rel": 0x80725AA0,
+ "d_a_e_nz.rel": 0x80729900,
+ "d_a_e_oc.rel": 0x8072C4E0,
+ "d_a_e_oct_bg.rel": 0x80736120,
+ "d_a_e_ot.rel": 0x8073A1E0,
+ "d_a_e_ph.rel": 0x8073D360,
+ "d_a_e_pm.rel": 0x80741E00,
+ "d_a_e_po.rel": 0x8074C460,
+ "d_a_e_pz.rel": 0x80758480,
+ "d_a_e_rb.rel": 0x807622A0,
+ "d_a_e_rdb.rel": 0x80764FE0,
+ "d_a_e_rdy.rel": 0x8076BCE0,
+ "d_a_e_s1.rel": 0x8077AAA0,
+ "d_a_e_sb.rel": 0x80781560,
+ "d_a_e_sf.rel": 0x80785040,
+ "d_a_e_sg.rel": 0x8078A140,
+ "d_a_e_sh.rel": 0x8078E260,
+ "d_a_e_sm.rel": 0x80792140,
+ "d_a_e_sm2.rel": 0x80798A60,
+ "d_a_e_st.rel": 0x8079DD00,
+ "d_a_e_st_line.rel": 0x807A6EE0,
+ "d_a_e_sw.rel": 0x807A7320,
+ "d_a_e_th.rel": 0x807B02A0,
+ "d_a_e_th_ball.rel": 0x807B48A0,
+ "d_a_e_tk.rel": 0x807B8100,
+ "d_a_e_tk2.rel": 0x807BA480,
+ "d_a_e_tk_ball.rel": 0x807BBF80,
+ "d_a_e_tt.rel": 0x807BD6C0,
+ "d_a_e_vt.rel": 0x807C2500,
+ "d_a_e_warpappear.rel": 0x807CF760,
+ "d_a_e_wb.rel": 0x807D23A0,
+ "d_a_e_ws.rel": 0x807E3920,
+ "d_a_e_ww.rel": 0x807E7600,
+ "d_a_e_yc.rel": 0x807EFDE0,
+ "d_a_e_yd.rel": 0x807F2B40,
+ "d_a_e_yd_leaf.rel": 0x807F7DA0,
+ "d_a_e_yg.rel": 0x807F8260,
+ "d_a_e_yh.rel": 0x807FD3E0,
+ "d_a_e_yk.rel": 0x80804740,
+ "d_a_e_ym.rel": 0x80808040,
+ "d_a_e_ym_tag.rel": 0x80815D80,
+ "d_a_e_ymb.rel": 0x80816020,
+ "d_a_e_yr.rel": 0x80822120,
+ "d_a_e_zh.rel": 0x80828F40,
+ "d_a_e_zm.rel": 0x8082F860,
+ "d_a_e_zs.rel": 0x80832FC0,
+ "d_a_formation_mng.rel": 0x808354E0,
+ "d_a_guard_mng.rel": 0x80837AA0,
+ "d_a_horse.rel": 0x80837E40,
+ "d_a_hozelda.rel": 0x80845E20,
+ "d_a_izumi_gate.rel": 0x80849020,
+ "d_a_kago.rel": 0x808494C0,
+ "d_a_kytag01.rel": 0x80854FE0,
+ "d_a_kytag02.rel": 0x80855A40,
+ "d_a_kytag03.rel": 0x80855E60,
+ "d_a_kytag06.rel": 0x80857780,
+ "d_a_kytag07.rel": 0x8085A200,
+ "d_a_kytag08.rel": 0x8085A500,
+ "d_a_kytag09.rel": 0x8085B380,
+ "d_a_kytag12.rel": 0x8085BAC0,
+ "d_a_kytag13.rel": 0x8085F180,
+ "d_a_kytag15.rel": 0x808605C0,
+ "d_a_kytag16.rel": 0x80860BE0,
+ "d_a_mant.rel": 0x80861220,
+ "d_a_mg_fshop.rel": 0x8086BF80,
+ "d_a_mirror.rel": 0x80870B40,
+ "d_a_movie_player.rel": 0x808726E0,
+ "d_a_myna.rel": 0x80945B80,
+ "d_a_ni.rel": 0x8094BB40,
+ "d_a_npc_aru.rel": 0x809516A0,
+ "d_a_npc_ash.rel": 0x80958200,
+ "d_a_npc_ashB.rel": 0x8095DD60,
+ "d_a_npc_bans.rel": 0x809627E0,
+ "d_a_npc_blue_ns.rel": 0x80968880,
+ "d_a_npc_bou.rel": 0x8096CEA0,
+ "d_a_npc_bouS.rel": 0x80973460,
+ "d_a_npc_cdn3.rel": 0x80978C60,
+ "d_a_npc_chat.rel": 0x80980760,
+ "d_a_npc_chin.rel": 0x8098BE20,
+ "d_a_npc_clerka.rel": 0x80992440,
+ "d_a_npc_clerkb.rel": 0x80995E40,
+ "d_a_npc_clerkt.rel": 0x8099A060,
+ "d_a_npc_coach.rel": 0x8099D660,
+ "d_a_npc_df.rel": 0x809A52A0,
+ "d_a_npc_doc.rel": 0x809A6BA0,
+ "d_a_npc_doorboy.rel": 0x809AABA0,
+ "d_a_npc_drainSol.rel": 0x809ADD80,
+ "d_a_npc_du.rel": 0x809AFC60,
+ "d_a_npc_fairy.rel": 0x809B1A20,
+ "d_a_npc_fguard.rel": 0x809BA440,
+ "d_a_npc_gnd.rel": 0x809BB520,
+ "d_a_npc_gra.rel": 0x809BE860,
+ "d_a_npc_grc.rel": 0x809CB3E0,
+ "d_a_npc_grd.rel": 0x809CFC40,
+ "d_a_npc_grm.rel": 0x809D3F80,
+ "d_a_npc_grmc.rel": 0x809D7320,
+ "d_a_npc_gro.rel": 0x809DA480,
+ "d_a_npc_grr.rel": 0x809DF7E0,
+ "d_a_npc_grs.rel": 0x809E3FE0,
+ "d_a_npc_grz.rel": 0x809E8320,
+ "d_a_npc_guard.rel": 0x809EFCE0,
+ "d_a_npc_gwolf.rel": 0x809F2FC0,
+ "d_a_npc_hanjo.rel": 0x809F8FA0,
+ "d_a_npc_henna0.rel": 0x80A01360,
+ "d_a_npc_hoz.rel": 0x80A01420,
+ "d_a_npc_impal.rel": 0x80A07900,
+ "d_a_npc_inko.rel": 0x80A0C920,
+ "d_a_npc_ins.rel": 0x80A0E160,
+ "d_a_npc_jagar.rel": 0x80A14620,
+ "d_a_npc_kasi_hana.rel": 0x80A1AEC0,
+ "d_a_npc_kasi_kyu.rel": 0x80A218E0,
+ "d_a_npc_kasi_mich.rel": 0x80A260E0,
+ "d_a_npc_kdk.rel": 0x80A2A860,
+ "d_a_npc_kn.rel": 0x80A2A920,
+ "d_a_npc_knj.rel": 0x80A43480,
+ "d_a_npc_kolinb.rel": 0x80A458A0,
+ "d_a_npc_ks.rel": 0x80A48EA0,
+ "d_a_npc_kyury.rel": 0x80A5FFE0,
+ "d_a_npc_len.rel": 0x80A64240,
+ "d_a_npc_lf.rel": 0x80A69A80,
+ "d_a_npc_lud.rel": 0x80A6AAC0,
+ "d_a_npc_midp.rel": 0x80A70B80,
+ "d_a_npc_mk.rel": 0x80A73D60,
+ "d_a_npc_moi.rel": 0x80A73E60,
+ "d_a_npc_moir.rel": 0x80A7C080,
+ "d_a_npc_myna2.rel": 0x80A83DA0,
+ "d_a_npc_ne.rel": 0x80A88BE0,
+ "d_a_npc_p2.rel": 0x80A92B80,
+ "d_a_npc_pachi_besu.rel": 0x80A92C80,
+ "d_a_npc_pachi_maro.rel": 0x80A97BA0,
+ "d_a_npc_pachi_taro.rel": 0x80A9C1A0,
+ "d_a_npc_passer.rel": 0x80AA2BA0,
+ "d_a_npc_passer2.rel": 0x80AA7460,
+ "d_a_npc_post.rel": 0x80AA8BA0,
+ "d_a_npc_pouya.rel": 0x80AADCC0,
+ "d_a_npc_prayer.rel": 0x80AB2D00,
+ "d_a_npc_raca.rel": 0x80AB5BC0,
+ "d_a_npc_rafrel.rel": 0x80AB9540,
+ "d_a_npc_saru.rel": 0x80AC0340,
+ "d_a_npc_seib.rel": 0x80AC5000,
+ "d_a_npc_seic.rel": 0x80AC7460,
+ "d_a_npc_seid.rel": 0x80AC95A0,
+ "d_a_npc_seira.rel": 0x80ACB6E0,
+ "d_a_npc_seira2.rel": 0x80AD0B20,
+ "d_a_npc_seirei.rel": 0x80AD5640,
+ "d_a_npc_shad.rel": 0x80AD8120,
+ "d_a_npc_shaman.rel": 0x80AE2CE0,
+ "d_a_npc_shoe.rel": 0x80AE7860,
+ "d_a_npc_shop0.rel": 0x80AEA760,
+ "d_a_npc_shop_maro.rel": 0x80AEBDE0,
+ "d_a_npc_sola.rel": 0x80AECAC0,
+ "d_a_npc_soldierA.rel": 0x80AEF4A0,
+ "d_a_npc_soldierB.rel": 0x80AF2BC0,
+ "d_a_npc_sq.rel": 0x80AF5F00,
+ "d_a_npc_the.rel": 0x80AF75E0,
+ "d_a_npc_theB.rel": 0x80AFC680,
+ "d_a_npc_tk.rel": 0x80B01320,
+ "d_a_npc_tkc.rel": 0x80B0C6E0,
+ "d_a_npc_tkj2.rel": 0x80B10D00,
+ "d_a_npc_tks.rel": 0x80B14540,
+ "d_a_npc_toby.rel": 0x80B1E560,
+ "d_a_npc_tr.rel": 0x80B25920,
+ "d_a_npc_uri.rel": 0x80B266C0,
+ "d_a_npc_worm.rel": 0x80B2DE00,
+ "d_a_npc_wrestler.rel": 0x80B2F1A0,
+ "d_a_npc_yamid.rel": 0x80B42E60,
+ "d_a_npc_yamis.rel": 0x80B46480,
+ "d_a_npc_yamit.rel": 0x80B49B00,
+ "d_a_npc_yelia.rel": 0x80B4D220,
+ "d_a_npc_ykm.rel": 0x80B53400,
+ "d_a_npc_ykw.rel": 0x80B5EE20,
+ "d_a_npc_zanb.rel": 0x80B68E40,
+ "d_a_npc_zant.rel": 0x80B6C0C0,
+ "d_a_npc_zelR.rel": 0x80B6ED40,
+ "d_a_npc_zelRo.rel": 0x80B71EC0,
+ "d_a_npc_zelda.rel": 0x80B75040,
+ "d_a_npc_zra.rel": 0x80B78420,
+ "d_a_npc_zrc.rel": 0x80B8DB20,
+ "d_a_npc_zrz.rel": 0x80B93CE0,
+ "d_a_obj_Lv5Key.rel": 0x80B9B940,
+ "d_a_obj_Turara.rel": 0x80B9CAA0,
+ "d_a_obj_TvCdlst.rel": 0x80B9EBE0,
+ "d_a_obj_Y_taihou.rel": 0x80B9FA00,
+ "d_a_obj_amiShutter.rel": 0x80BA13E0,
+ "d_a_obj_ari.rel": 0x80BA25A0,
+ "d_a_obj_automata.rel": 0x80BA5600,
+ "d_a_obj_avalanche.rel": 0x80BA6E60,
+ "d_a_obj_balloon.rel": 0x80BA7EC0,
+ "d_a_obj_barDesk.rel": 0x80BA90A0,
+ "d_a_obj_batta.rel": 0x80BA9D40,
+ "d_a_obj_bbox.rel": 0x80BACCC0,
+ "d_a_obj_bed.rel": 0x80BAD580,
+ "d_a_obj_bemos.rel": 0x80BAE280,
+ "d_a_obj_bhbridge.rel": 0x80BB52A0,
+ "d_a_obj_bk_leaf.rel": 0x80BB6060,
+ "d_a_obj_bky_rock.rel": 0x80BB67C0,
+ "d_a_obj_bmWindow.rel": 0x80BB8120,
+ "d_a_obj_bmshutter.rel": 0x80BB99E0,
+ "d_a_obj_bombf.rel": 0x80BBA980,
+ "d_a_obj_boumato.rel": 0x80BBAF00,
+ "d_a_obj_brg.rel": 0x80BBC820,
+ "d_a_obj_bsGate.rel": 0x80BC27A0,
+ "d_a_obj_bubblePilar.rel": 0x80BC3300,
+ "d_a_obj_catdoor.rel": 0x80BC4240,
+ "d_a_obj_cb.rel": 0x80BC4A40,
+ "d_a_obj_cblock.rel": 0x80BC5920,
+ "d_a_obj_cdoor.rel": 0x80BC6D40,
+ "d_a_obj_chandelier.rel": 0x80BC7EC0,
+ "d_a_obj_chest.rel": 0x80BC8E80,
+ "d_a_obj_cho.rel": 0x80BCA220,
+ "d_a_obj_cowdoor.rel": 0x80BCC780,
+ "d_a_obj_crope.rel": 0x80BCCC60,
+ "d_a_obj_crvfence.rel": 0x80BCEC20,
+ "d_a_obj_crvgate.rel": 0x80BD0320,
+ "d_a_obj_crvhahen.rel": 0x80BD32C0,
+ "d_a_obj_crvlh_down.rel": 0x80BD40C0,
+ "d_a_obj_crvlh_up.rel": 0x80BD4E60,
+ "d_a_obj_crvsteel.rel": 0x80BD5B40,
+ "d_a_obj_crystal.rel": 0x80BD6320,
+ "d_a_obj_cwall.rel": 0x80BD6960,
+ "d_a_obj_damCps.rel": 0x80BDA3E0,
+ "d_a_obj_dan.rel": 0x80BDA4A0,
+ "d_a_obj_digholl.rel": 0x80BDC5A0,
+ "d_a_obj_digsnow.rel": 0x80BDCBE0,
+ "d_a_obj_dmelevator.rel": 0x80BDD880,
+ "d_a_obj_drop.rel": 0x80BDFC60,
+ "d_a_obj_dust.rel": 0x80BE2240,
+ "d_a_obj_enemy_create.rel": 0x80BE3100,
+ "d_a_obj_fallobj.rel": 0x80BE3940,
+ "d_a_obj_fan.rel": 0x80BE4BC0,
+ "d_a_obj_fchain.rel": 0x80BE5F80,
+ "d_a_obj_fireWood.rel": 0x80BE7540,
+ "d_a_obj_fireWood2.rel": 0x80BE8160,
+ "d_a_obj_firepillar.rel": 0x80BE90A0,
+ "d_a_obj_firepillar2.rel": 0x80BE9C40,
+ "d_a_obj_flag.rel": 0x80BEB700,
+ "d_a_obj_flag2.rel": 0x80BEC5E0,
+ "d_a_obj_flag3.rel": 0x80BEEA00,
+ "d_a_obj_food.rel": 0x80BF0620,
+ "d_a_obj_fw.rel": 0x80BF1F60,
+ "d_a_obj_gadget.rel": 0x80BF2C40,
+ "d_a_obj_ganonwall.rel": 0x80BF4C80,
+ "d_a_obj_ganonwall2.rel": 0x80BF5760,
+ "d_a_obj_gb.rel": 0x80BF62A0,
+ "d_a_obj_geyser.rel": 0x80BF6EA0,
+ "d_a_obj_glowSphere.rel": 0x80BF9260,
+ "d_a_obj_gm.rel": 0x80BFB060,
+ "d_a_obj_goGate.rel": 0x80BFD5C0,
+ "d_a_obj_gomikabe.rel": 0x80BFE140,
+ "d_a_obj_gra2.rel": 0x80BFFE20,
+ "d_a_obj_graWall.rel": 0x80C10B80,
+ "d_a_obj_gra_rock.rel": 0x80C11080,
+ "d_a_obj_grave_stone.rel": 0x80C12580,
+ "d_a_obj_groundwater.rel": 0x80C133C0,
+ "d_a_obj_grz_rock.rel": 0x80C14B60,
+ "d_a_obj_h_saku.rel": 0x80C15440,
+ "d_a_obj_hakai_brl.rel": 0x80C166C0,
+ "d_a_obj_hakai_ftr.rel": 0x80C17420,
+ "d_a_obj_hasu2.rel": 0x80C18220,
+ "d_a_obj_hata.rel": 0x80C18B40,
+ "d_a_obj_hb.rel": 0x80C19540,
+ "d_a_obj_hbombkoya.rel": 0x80C1B800,
+ "d_a_obj_heavySw.rel": 0x80C1CA00,
+ "d_a_obj_hfuta.rel": 0x80C1DCA0,
+ "d_a_obj_hsTarget.rel": 0x80C1F340,
+ "d_a_obj_ice_l.rel": 0x80C1F980,
+ "d_a_obj_ice_s.rel": 0x80C208C0,
+ "d_a_obj_iceblock.rel": 0x80C21C60,
+ "d_a_obj_iceleaf.rel": 0x80C246A0,
+ "d_a_obj_ihasi.rel": 0x80C25FA0,
+ "d_a_obj_ikada.rel": 0x80C26940,
+ "d_a_obj_inobone.rel": 0x80C27640,
+ "d_a_obj_ita.rel": 0x80C28280,
+ "d_a_obj_itamato.rel": 0x80C29400,
+ "d_a_obj_kabuto.rel": 0x80C2AD80,
+ "d_a_obj_kag.rel": 0x80C2E320,
+ "d_a_obj_kage.rel": 0x80C31460,
+ "d_a_obj_kago.rel": 0x80C31A00,
+ "d_a_obj_kaisou.rel": 0x80C33FE0,
+ "d_a_obj_kamakiri.rel": 0x80C350A0,
+ "d_a_obj_kantera.rel": 0x80C38600,
+ "d_a_obj_katatsumuri.rel": 0x80C39880,
+ "d_a_obj_kazeneko.rel": 0x80C3C860,
+ "d_a_obj_kbox.rel": 0x80C3D520,
+ "d_a_obj_key.rel": 0x80C3F320,
+ "d_a_obj_keyhole.rel": 0x80C41160,
+ "d_a_obj_ki.rel": 0x80C43E00,
+ "d_a_obj_kiPot.rel": 0x80C44C20,
+ "d_a_obj_kita.rel": 0x80C45260,
+ "d_a_obj_kjgjs.rel": 0x80C46020,
+ "d_a_obj_kkanban.rel": 0x80C46620,
+ "d_a_obj_knBullet.rel": 0x80C470A0,
+ "d_a_obj_kshutter.rel": 0x80C47A40,
+ "d_a_obj_kuwagata.rel": 0x80C4A100,
+ "d_a_obj_kwheel00.rel": 0x80C4D680,
+ "d_a_obj_kwheel01.rel": 0x80C4EA00,
+ "d_a_obj_kznkarm.rel": 0x80C4F7E0,
+ "d_a_obj_laundry.rel": 0x80C50F20,
+ "d_a_obj_laundry_rope.rel": 0x80C52180,
+ "d_a_obj_lbox.rel": 0x80C53480,
+ "d_a_obj_lp.rel": 0x80C54120,
+ "d_a_obj_lv1Candle00.rel": 0x80C55C20,
+ "d_a_obj_lv1Candle01.rel": 0x80C56BC0,
+ "d_a_obj_lv3Candle.rel": 0x80C579E0,
+ "d_a_obj_lv3Water.rel": 0x80C586E0,
+ "d_a_obj_lv3Water2.rel": 0x80C5A320,
+ "d_a_obj_lv3WaterB.rel": 0x80C5B500,
+ "d_a_obj_lv3saka00.rel": 0x80C5BDE0,
+ "d_a_obj_lv3waterEff.rel": 0x80C5C480,
+ "d_a_obj_lv4CandleDemoTag.rel": 0x80C5C900,
+ "d_a_obj_lv4CandleTag.rel": 0x80C5D360,
+ "d_a_obj_lv4EdShutter.rel": 0x80C5DCC0,
+ "d_a_obj_lv4Gate.rel": 0x80C5EA80,
+ "d_a_obj_lv4HsTarget.rel": 0x80C5F540,
+ "d_a_obj_lv4PoGate.rel": 0x80C5FB00,
+ "d_a_obj_lv4RailWall.rel": 0x80C60A00,
+ "d_a_obj_lv4SlideWall.rel": 0x80C61D40,
+ "d_a_obj_lv4bridge.rel": 0x80C62800,
+ "d_a_obj_lv4chandelier.rel": 0x80C632C0,
+ "d_a_obj_lv4digsand.rel": 0x80C66AA0,
+ "d_a_obj_lv4floor.rel": 0x80C67780,
+ "d_a_obj_lv4gear.rel": 0x80C67EA0,
+ "d_a_obj_lv4prelvtr.rel": 0x80C68580,
+ "d_a_obj_lv4prwall.rel": 0x80C68C20,
+ "d_a_obj_lv4sand.rel": 0x80C69A20,
+ "d_a_obj_lv5FloorBoard.rel": 0x80C6A520,
+ "d_a_obj_lv5IceWall.rel": 0x80C6B340,
+ "d_a_obj_lv5SwIce.rel": 0x80C6C860,
+ "d_a_obj_lv5ychndlr.rel": 0x80C6D6E0,
+ "d_a_obj_lv5yiblltray.rel": 0x80C6EB80,
+ "d_a_obj_lv6ChangeGate.rel": 0x80C70B60,
+ "d_a_obj_lv6FurikoTrap.rel": 0x80C723A0,
+ "d_a_obj_lv6Lblock.rel": 0x80C73300,
+ "d_a_obj_lv6SwGate.rel": 0x80C73E20,
+ "d_a_obj_lv6SzGate.rel": 0x80C75780,
+ "d_a_obj_lv6Tenbin.rel": 0x80C76800,
+ "d_a_obj_lv6TogeRoll.rel": 0x80C77B40,
+ "d_a_obj_lv6TogeTrap.rel": 0x80C79D60,
+ "d_a_obj_lv6bemos.rel": 0x80C7CAA0,
+ "d_a_obj_lv6bemos2.rel": 0x80C7E140,
+ "d_a_obj_lv6egate.rel": 0x80C82000,
+ "d_a_obj_lv6elevta.rel": 0x80C82CC0,
+ "d_a_obj_lv6swturn.rel": 0x80C83920,
+ "d_a_obj_lv7BsGate.rel": 0x80C847C0,
+ "d_a_obj_lv7PropellerY.rel": 0x80C85380,
+ "d_a_obj_lv7bridge.rel": 0x80C86300,
+ "d_a_obj_lv8KekkaiTrap.rel": 0x80C87BC0,
+ "d_a_obj_lv8Lift.rel": 0x80C88640,
+ "d_a_obj_lv8OptiLift.rel": 0x80C8A220,
+ "d_a_obj_lv8UdFloor.rel": 0x80C8BC20,
+ "d_a_obj_lv9SwShutter.rel": 0x80C8CDC0,
+ "d_a_obj_magLift.rel": 0x80C8D940,
+ "d_a_obj_magLiftRot.rel": 0x80C8E860,
+ "d_a_obj_maki.rel": 0x80C8FD20,
+ "d_a_obj_master_sword.rel": 0x80C90A80,
+ "d_a_obj_mato.rel": 0x80C91AE0,
+ "d_a_obj_mhole.rel": 0x80C92DE0,
+ "d_a_obj_mie.rel": 0x80C93EE0,
+ "d_a_obj_mirror_6pole.rel": 0x80C95DA0,
+ "d_a_obj_mirror_chain.rel": 0x80C96620,
+ "d_a_obj_mirror_sand.rel": 0x80C98160,
+ "d_a_obj_mirror_screw.rel": 0x80C98A60,
+ "d_a_obj_mirror_table.rel": 0x80C998A0,
+ "d_a_obj_msima.rel": 0x80C9B320,
+ "d_a_obj_mvstair.rel": 0x80C9D020,
+ "d_a_obj_myogan.rel": 0x80C9EAC0,
+ "d_a_obj_nagaisu.rel": 0x80C9F340,
+ "d_a_obj_nan.rel": 0x80CA04C0,
+ "d_a_obj_ndoor.rel": 0x80CA34C0,
+ "d_a_obj_nougu.rel": 0x80CA3B20,
+ "d_a_obj_octhashi.rel": 0x80CA4B40,
+ "d_a_obj_oiltubo.rel": 0x80CA66A0,
+ "d_a_obj_onsen.rel": 0x80CA7AE0,
+ "d_a_obj_onsenFire.rel": 0x80CA8200,
+ "d_a_obj_onsenTaru.rel": 0x80CA84E0,
+ "d_a_obj_pdoor.rel": 0x80CA9E20,
+ "d_a_obj_pdtile.rel": 0x80CAAC40,
+ "d_a_obj_pdwall.rel": 0x80CAC780,
+ "d_a_obj_picture.rel": 0x80CAD280,
+ "d_a_obj_pillar.rel": 0x80CAF240,
+ "d_a_obj_pleaf.rel": 0x80CB0C60,
+ "d_a_obj_poCandle.rel": 0x80CB1980,
+ "d_a_obj_poFire.rel": 0x80CB2860,
+ "d_a_obj_poTbox.rel": 0x80CB4160,
+ "d_a_obj_prop.rel": 0x80CB5160,
+ "d_a_obj_pumpkin.rel": 0x80CB56A0,
+ "d_a_obj_rcircle.rel": 0x80CB85E0,
+ "d_a_obj_rfHole.rel": 0x80CB8D80,
+ "d_a_obj_rgate.rel": 0x80CB9C20,
+ "d_a_obj_riverrock.rel": 0x80CBC5E0,
+ "d_a_obj_rock.rel": 0x80CBDC20,
+ "d_a_obj_rotBridge.rel": 0x80CBE8A0,
+ "d_a_obj_rotTrap.rel": 0x80CBF7C0,
+ "d_a_obj_roten.rel": 0x80CC0AE0,
+ "d_a_obj_rstair.rel": 0x80CC14E0,
+ "d_a_obj_rw.rel": 0x80CC28A0,
+ "d_a_obj_saidan.rel": 0x80CC3CC0,
+ "d_a_obj_sakuita.rel": 0x80CC4680,
+ "d_a_obj_sakuita_rope.rel": 0x80CC51C0,
+ "d_a_obj_scannon.rel": 0x80CC6A20,
+ "d_a_obj_scannon_crs.rel": 0x80CC9600,
+ "d_a_obj_scannon_ten.rel": 0x80CCB2A0,
+ "d_a_obj_sekidoor.rel": 0x80CCCF40,
+ "d_a_obj_sekizo.rel": 0x80CCDB20,
+ "d_a_obj_sekizoa.rel": 0x80CCE260,
+ "d_a_obj_shield.rel": 0x80CD69E0,
+ "d_a_obj_sm_door.rel": 0x80CD8540,
+ "d_a_obj_smallkey.rel": 0x80CD9740,
+ "d_a_obj_smgdoor.rel": 0x80CDBAC0,
+ "d_a_obj_smoke.rel": 0x80CDCE00,
+ "d_a_obj_smtile.rel": 0x80CDD1C0,
+ "d_a_obj_smw_stone.rel": 0x80CDE4A0,
+ "d_a_obj_snowEffTag.rel": 0x80CDEFC0,
+ "d_a_obj_snow_soup.rel": 0x80CDF7E0,
+ "d_a_obj_so.rel": 0x80CE02E0,
+ "d_a_obj_spinLift.rel": 0x80CE3CC0,
+ "d_a_obj_ss_drink.rel": 0x80CE4F00,
+ "d_a_obj_ss_item.rel": 0x80CE6BA0,
+ "d_a_obj_stairBlock.rel": 0x80CE8080,
+ "d_a_obj_stone.rel": 0x80CE9000,
+ "d_a_obj_stopper.rel": 0x80CECE20,
+ "d_a_obj_stopper2.rel": 0x80CEF2C0,
+ "d_a_obj_suisya.rel": 0x80CF0000,
+ "d_a_obj_sw.rel": 0x80CF05C0,
+ "d_a_obj_swBallA.rel": 0x80CF3280,
+ "d_a_obj_swBallB.rel": 0x80CF4540,
+ "d_a_obj_swBallC.rel": 0x80CF5B20,
+ "d_a_obj_swLight.rel": 0x80CF6F20,
+ "d_a_obj_swchain.rel": 0x80CF8640,
+ "d_a_obj_swhang.rel": 0x80CFB8C0,
+ "d_a_obj_sword.rel": 0x80CFD4E0,
+ "d_a_obj_swpush2.rel": 0x80CFE020,
+ "d_a_obj_swspinner.rel": 0x80CFFF00,
+ "d_a_obj_swturn.rel": 0x80D00B60,
+ "d_a_obj_syRock.rel": 0x80D021C0,
+ "d_a_obj_szbridge.rel": 0x80D042C0,
+ "d_a_obj_taFence.rel": 0x80D04D80,
+ "d_a_obj_table.rel": 0x80D063C0,
+ "d_a_obj_takaraDai.rel": 0x80D06CA0,
+ "d_a_obj_tatigi.rel": 0x80D07960,
+ "d_a_obj_ten.rel": 0x80D086E0,
+ "d_a_obj_testcube.rel": 0x80D0BAE0,
+ "d_a_obj_tgake.rel": 0x80D0BBA0,
+ "d_a_obj_thashi.rel": 0x80D0C1C0,
+ "d_a_obj_thdoor.rel": 0x80D0D480,
+ "d_a_obj_timeFire.rel": 0x80D0E800,
+ "d_a_obj_tks.rel": 0x80D0F1A0,
+ "d_a_obj_tmoon.rel": 0x80D12B20,
+ "d_a_obj_toaru_maki.rel": 0x80D13000,
+ "d_a_obj_toby.rel": 0x80D136A0,
+ "d_a_obj_tobyhouse.rel": 0x80D159C0,
+ "d_a_obj_togeTrap.rel": 0x80D17A80,
+ "d_a_obj_tombo.rel": 0x80D190E0,
+ "d_a_obj_tornado.rel": 0x80D1B920,
+ "d_a_obj_tornado2.rel": 0x80D1C460,
+ "d_a_obj_tp.rel": 0x80D1D500,
+ "d_a_obj_treesh.rel": 0x80D1EFE0,
+ "d_a_obj_twGate.rel": 0x80D1F9C0,
+ "d_a_obj_udoor.rel": 0x80D206C0,
+ "d_a_obj_usaku.rel": 0x80D20EE0,
+ "d_a_obj_vground.rel": 0x80D21360,
+ "d_a_obj_volcball.rel": 0x80D21AA0,
+ "d_a_obj_volcbom.rel": 0x80D24120,
+ "d_a_obj_warp_kbrg.rel": 0x80D26EC0,
+ "d_a_obj_warp_obrg.rel": 0x80D29940,
+ "d_a_obj_waterGate.rel": 0x80D2BAA0,
+ "d_a_obj_waterPillar.rel": 0x80D2C5E0,
+ "d_a_obj_waterfall.rel": 0x80D2EBA0,
+ "d_a_obj_wchain.rel": 0x80D2FE80,
+ "d_a_obj_wdStick.rel": 0x80D31A60,
+ "d_a_obj_web0.rel": 0x80D34440,
+ "d_a_obj_web1.rel": 0x80D352C0,
+ "d_a_obj_well_cover.rel": 0x80D36220,
+ "d_a_obj_wflag.rel": 0x80D36B20,
+ "d_a_obj_wind_stone.rel": 0x80D37980,
+ "d_a_obj_window.rel": 0x80D386E0,
+ "d_a_obj_wood_pendulum.rel": 0x80D39380,
+ "d_a_obj_wood_statue.rel": 0x80D39DC0,
+ "d_a_obj_wsword.rel": 0x80D3B900,
+ "d_a_obj_yel_bag.rel": 0x80D3C000,
+ "d_a_obj_ystone.rel": 0x80D3DFA0,
+ "d_a_obj_zcloth.rel": 0x80D3EDA0,
+ "d_a_obj_zdoor.rel": 0x80D3F3C0,
+ "d_a_obj_zrTurara.rel": 0x80D40480,
+ "d_a_obj_zrTuraraRock.rel": 0x80D417A0,
+ "d_a_obj_zraMark.rel": 0x80D425E0,
+ "d_a_obj_zra_freeze.rel": 0x80D44040,
+ "d_a_obj_zra_rock.rel": 0x80D44C40,
+ "d_a_passer_mng.rel": 0x80D456A0,
+ "d_a_peru.rel": 0x80D46E00,
+ "d_a_ppolamp.rel": 0x80D4C8C0,
+ "d_a_skip_2D.rel": 0x80D4D400,
+ "d_a_startAndGoal.rel": 0x80D4D740,
+ "d_a_swBall.rel": 0x80D4DF60,
+ "d_a_swLBall.rel": 0x80D4EA80,
+ "d_a_swTime.rel": 0x80D4F5C0,
+ "d_a_tag_Lv6Gate.rel": 0x80D4F820,
+ "d_a_tag_Lv7Gate.rel": 0x80D50A40,
+ "d_a_tag_Lv8Gate.rel": 0x80D51BC0,
+ "d_a_tag_TWgate.rel": 0x80D52580,
+ "d_a_tag_arena.rel": 0x80D55C60,
+ "d_a_tag_assistance.rel": 0x80D55DC0,
+ "d_a_tag_bottle_item.rel": 0x80D55F60,
+ "d_a_tag_chgrestart.rel": 0x80D566E0,
+ "d_a_tag_csw.rel": 0x80D56B60,
+ "d_a_tag_escape.rel": 0x80D58760,
+ "d_a_tag_firewall.rel": 0x80D588C0,
+ "d_a_tag_gra.rel": 0x80D595E0,
+ "d_a_tag_guard.rel": 0x80D59780,
+ "d_a_tag_instruction.rel": 0x80D59A80,
+ "d_a_tag_kago_fall.rel": 0x80D59BE0,
+ "d_a_tag_lightball.rel": 0x80D5A780,
+ "d_a_tag_lv5soup.rel": 0x80D5ACE0,
+ "d_a_tag_lv6CstaSw.rel": 0x80D5B200,
+ "d_a_tag_mmsg.rel": 0x80D5B8A0,
+ "d_a_tag_mwait.rel": 0x80D5BDA0,
+ "d_a_tag_myna2.rel": 0x80D5C620,
+ "d_a_tag_myna_light.rel": 0x80D5CAC0,
+ "d_a_tag_pachi.rel": 0x80D5D3C0,
+ "d_a_tag_poFire.rel": 0x80D5D9E0,
+ "d_a_tag_qs.rel": 0x80D5DE00,
+ "d_a_tag_ret_room.rel": 0x80D5EE20,
+ "d_a_tag_river_back.rel": 0x80D5F280,
+ "d_a_tag_rmbit_sw.rel": 0x80D5FA80,
+ "d_a_tag_schedule.rel": 0x80D60020,
+ "d_a_tag_setBall.rel": 0x80D60180,
+ "d_a_tag_setrestart.rel": 0x80D60380,
+ "d_a_tag_shop_camera.rel": 0x80D60820,
+ "d_a_tag_shop_item.rel": 0x80D60B00,
+ "d_a_tag_smk_emt.rel": 0x80D61260,
+ "d_a_tag_spinner.rel": 0x80D61680,
+ "d_a_tag_sppath.rel": 0x80D61BA0,
+ "d_a_tag_ss_drink.rel": 0x80D62D40,
+ "d_a_tag_stream.rel": 0x80D63880,
+ "d_a_tag_theB_hint.rel": 0x80D63C60,
+ "d_a_tag_wara_howl.rel": 0x80D63EC0,
+ "d_a_tag_watchge.rel": 0x80D64260,
+ "d_a_tag_waterfall.rel": 0x80D64540,
+ "d_a_tag_wljump.rel": 0x80D64E40,
+ "d_a_tag_yami.rel": 0x80D65980,
+ "d_a_talk.rel": 0x80D66300,
+ "d_a_tboxSw.rel": 0x80D667E0,
+ "d_a_title.rel": 0x80D66A20,
+ "d_a_warp_bug.rel": 0x80D67DC0,
+}
+
+PREDEFINED_SYMBOLS = {
+ 0x80000030: "__arena_lo",
+ 0x80000034: "__arena_hi",
+ 0x80000040: "__debugger_present_flag",
+ 0x80000044: "__debugger_exception_mask",
+ 0x800000F4: "__dvd_bi2_location",
+ # 0x800030e4: "data_800030E4",
+ # 0x800030E8: "data_800030E8",
+ # 0x800030E6: "data_800030E6",
+ 0x80003100: "__check_pad3",
+ 0x80003140: "__set_debug_bba",
+ 0x8000314C: "__get_debug_bba",
+ 0x80003154: "__start",
+ 0x800032B0: "__init_registers",
+ 0x80003340: "__init_data",
+ 0x80003400: "__init_hardware",
+ 0x80003424: "__flush_cache",
+ 0x80003458: "memset",
+ 0x80003488: "__fill_mem",
+ 0x80003540: "memcpy",
+ 0x80003590: "TRK_memset",
+ 0x800035C0: "TRK_memcpy",
+ 0x80005518: "__TRK_reset",
+ 0x800035E4: "__TRK_unknown_data",
+ 0x80005544: "_rom_copy_info",
+ 0x800055C8: "_bss_init_info",
+}
+
+FAKE_FUNCTIONS = set(
+ [
+ 0x80005544,
+ 0x800055C8,
+ 0x8000569C,
+ 0x800035E4,
+ 0x800030E8,
+ 0x800030E6,
+ 0x80005544,
+ 0x800055C8,
+ ]
+)
+
+FOLDERS = [
+ ("d_bg", "d/bg/"),
+ ("d_cc", "d/cc/"),
+ ("d_com", "d/com/"),
+ ("d_event", "d/event/"),
+ ("d_file", "d/file/"),
+ ("d_map", "d/map/"),
+ ("d_menu", "d/menu/"),
+ ("d_meter", "d/meter/"),
+ ("d_msg_scrn", "d/msg/scrn/"),
+ ("d_msg", "d/msg/"),
+ ("d_ovlp", "d/ovlp/"),
+ ("d_pane", "d/pane/"),
+ ("d_particle", "d/particle/"),
+ ("d_kankyo", "d/kankyo/"),
+ ("d_save", "d/save/"),
+ ("d_shop", "d/shop/"),
+ ("d_a_kytag", "d/a/kytag/"),
+ ("d_a_door_", "d/a/door/"),
+ ("d_a_e_", "d/a/e/"),
+ ("d_a_npc_", "d/a/npc/"),
+ ("d_a_obj_mirror_", "d/a/obj/mirror/"),
+ ("d_a_obj_", "d/a/obj/"),
+ ("d_a_tag_", "d/a/tag/"),
+ ("d_a_b_", "d/a/b/"),
+ ("d_a_", "d/a/"),
+ ("d_s_", "d/s/"),
+ ("d_", "d/"),
+ ("c_", "c/"),
+ ("f_ap_", "f_ap/"),
+ ("f_op_", "f_op/"),
+ ("f_pc_", "f_pc/"),
+ ("m_Do_", "m_Do/"),
+]
+
+LIBRARY_LUT = [
+ ("ai", "dolphin/"),
+ ("ar", "dolphin/"),
+ ("base", "dolphin/"),
+ ("card", "dolphin/"),
+ ("db", "dolphin/"),
+ ("dsp", "dolphin/"),
+ ("dvd", "dolphin/"),
+ ("gd", "dolphin/"),
+ ("gf", "dolphin/"),
+ ("gx", "dolphin/"),
+ ("vi", "dolphin/"),
+ ("os", "dolphin/"),
+ ("pad", "dolphin/"),
+ ("si", "dolphin/"),
+ ("mtx", "dolphin/"),
+ ("S", "SSystem/"),
+ ("JStudio", "JSystem/JStudio/"),
+ ("J", "JSystem/"),
+]
+
+NAMESPACES = set(
+ [
+ "JStudio::fvb",
+ "JStudio::ctb",
+ "JStudio::stb",
+ "JStudio",
+ "JGadget",
+ "std",
+ ]
+)
diff --git a/tools/elf2dol/Makefile b/tools/elf2dol/Makefile
new file mode 100644
index 0000000000..ae02e50079
--- /dev/null
+++ b/tools/elf2dol/Makefile
@@ -0,0 +1,7 @@
+ELF2DOL_CC := cc
+ELF2DOL_CFLAGS := -O3 -Wall -s
+
+$(ELF2DOL): include tools/elf2dol/Makefile
+ @echo [tools] building elf2dol
+ @$(ELF2DOL_CC) $(ELF2DOL_CFLAGS) -o $(ELF2DOL) tools/elf2dol/elf2dol.c
+
diff --git a/tools/elf2dol.c b/tools/elf2dol/elf2dol.c
index aaa360ee2b..aaa360ee2b 100644
--- a/tools/elf2dol.c
+++ b/tools/elf2dol/elf2dol.c
diff --git a/tools/extract_game_assets.py b/tools/extract_game_assets.py
index c83f700847..77eca329ed 100644
--- a/tools/extract_game_assets.py
+++ b/tools/extract_game_assets.py
@@ -12,78 +12,115 @@ numFileEntries = 0
"""
Returns the offset address and size of fst.bin
"""
+
+
def getFstInfo(handler, fstOffsetPosition):
- fstOffset = int.from_bytes(bytearray(handler.read(4)), byteorder='big')
- handler.seek(fstOffsetPosition + 4) # Get the size which is 4 bytes after the offset
- fstSize = int.from_bytes(bytearray(handler.read(4)), byteorder='big')
+ fstOffset = int.from_bytes(bytearray(handler.read(4)), byteorder="big")
+ handler.seek(
+ fstOffsetPosition + 4
+ ) # Get the size which is 4 bytes after the offset
+ fstSize = int.from_bytes(bytearray(handler.read(4)), byteorder="big")
return fstOffset, fstSize
+
"""
Parses the fst.bin into a list of dictionaries containing
the file entry type, the file/folder name, the ISO file offset/parent file entry, the file size/last file entry
"""
+
+
def parseFstBin(fstBinBytes):
currentByte = 0
- numFileEntries = int.from_bytes(fstBinBytes[10:12], byteorder='big') # fst.bin offset
+ numFileEntries = int.from_bytes(
+ fstBinBytes[10:12], byteorder="big"
+ ) # fst.bin offset
stringTableOffset = numFileEntries * 0xC
ret = []
- while currentByte != (numFileEntries*12):
+ while currentByte != (numFileEntries * 12):
currentByte += 12
# lazy
- if currentByte == (numFileEntries*12):
+ if currentByte == (numFileEntries * 12):
break
fileFolder = fstBinBytes[currentByte]
- filenameOffset = int.from_bytes(fstBinBytes[currentByte+1:currentByte+4], byteorder='big')
- fileOffsetOrParentEntryNum = int.from_bytes(fstBinBytes[currentByte+4:currentByte+8], byteorder='big')
- fileSizeOrLastEntryNum = int.from_bytes(fstBinBytes[currentByte+8:currentByte+12], byteorder='big')
- currentFilenameOffset = stringTableOffset+filenameOffset
+ filenameOffset = int.from_bytes(
+ fstBinBytes[currentByte + 1 : currentByte + 4], byteorder="big"
+ )
+ fileOffsetOrParentEntryNum = int.from_bytes(
+ fstBinBytes[currentByte + 4 : currentByte + 8], byteorder="big"
+ )
+ fileSizeOrLastEntryNum = int.from_bytes(
+ fstBinBytes[currentByte + 8 : currentByte + 12], byteorder="big"
+ )
+ currentFilenameOffset = stringTableOffset + filenameOffset
# Figure out the filename by checking for null string terminator
i = 0
- while fstBinBytes[currentFilenameOffset+i] != 0:
+ while fstBinBytes[currentFilenameOffset + i] != 0:
i += 1
- fileName = (fstBinBytes[currentFilenameOffset:currentFilenameOffset+i]).decode()
+ fileName = (
+ fstBinBytes[currentFilenameOffset : currentFilenameOffset + i]
+ ).decode()
if fileFolder == 0:
- ret.append({"type": "File","fileName": fileName,"fileOffset":fileOffsetOrParentEntryNum,"fileSize":fileSizeOrLastEntryNum})
+ ret.append(
+ {
+ "type": "File",
+ "fileName": fileName,
+ "fileOffset": fileOffsetOrParentEntryNum,
+ "fileSize": fileSizeOrLastEntryNum,
+ }
+ )
else:
- ret.append({"type": "Folder","folderName": fileName,"parentFolderEntryNumber": fileOffsetOrParentEntryNum, "lastEntryNumber": fileSizeOrLastEntryNum})
-
+ ret.append(
+ {
+ "type": "Folder",
+ "folderName": fileName,
+ "parentFolderEntryNumber": fileOffsetOrParentEntryNum,
+ "lastEntryNumber": fileSizeOrLastEntryNum,
+ }
+ )
+
return ret
+
"""
Write the current folder to disk and return it's name/last entry number
"""
-def writeFolder(parsedFstBin,i):
- folderPath = i["folderName"]+"/"
+
+
+def writeFolder(parsedFstBin, i):
+ folderPath = i["folderName"] + "/"
lastEntryNumber = i["lastEntryNumber"]
if i["parentFolderEntryNumber"] == 0:
if not os.path.exists(folderPath):
os.makedirs(folderPath)
else:
- parentFolderEntry = parsedFstBin[i["parentFolderEntryNumber"]-1]
+ parentFolderEntry = parsedFstBin[i["parentFolderEntryNumber"] - 1]
while True:
folderPath = parentFolderEntry["folderName"] + "/" + folderPath
if parentFolderEntry["parentFolderEntryNumber"] == 0:
break
nextParentFolderEntryNumber = parentFolderEntry["parentFolderEntryNumber"]
- parentFolderEntry = parsedFstBin[nextParentFolderEntryNumber-1]
-
+ parentFolderEntry = parsedFstBin[nextParentFolderEntryNumber - 1]
+
if not os.path.exists(folderPath):
os.makedirs(folderPath)
return folderPath, lastEntryNumber
+
"""
Use the parsed fst.bin contents to write assets to file
"""
+
+
def writeAssets(parsedFstBin, handler):
# Write the folder structure and files to disc
j = 0
@@ -92,21 +129,26 @@ def writeAssets(parsedFstBin, handler):
for i in parsedFstBin:
j += 1
if i["type"] == "Folder":
- currentFolder, lastEntryNumber = writeFolder(parsedFstBin,i)
- folderStack.append({"folderName": currentFolder, "lastEntryNumber": lastEntryNumber})
+ currentFolder, lastEntryNumber = writeFolder(parsedFstBin, i)
+ folderStack.append(
+ {"folderName": currentFolder, "lastEntryNumber": lastEntryNumber}
+ )
else:
handler.seek(i["fileOffset"])
- with open((folderStack[-1]["folderName"]+i["fileName"]), "wb") as currentFile:
+ with open(
+ (folderStack[-1]["folderName"] + i["fileName"]), "wb"
+ ) as currentFile:
currentFile.write(bytearray(handler.read(i["fileSize"])))
-
- while folderStack[-1]["lastEntryNumber"] == j+1:
+
+ while folderStack[-1]["lastEntryNumber"] == j + 1:
folderStack.pop()
-def main():
- with open(sys.argv[1], "rb") as f:
+
+def extract(path):
+ with open(path, "rb") as f:
# Seek to fst offset information and retrieve it
f.seek(fstInfoPosition)
- fstOffset,fstSize = getFstInfo(f,fstInfoPosition)
+ fstOffset, fstSize = getFstInfo(f, fstInfoPosition)
# Seek to fst.bin and retrieve it
f.seek(fstOffset)
@@ -118,5 +160,10 @@ def main():
# Write assets to file
writeAssets(parsedFstBin, f)
+
+def main():
+ extract(sys.argv[1], "rb")
+
+
if __name__ == "__main__":
- main() \ No newline at end of file
+ main()
diff --git a/tools/find_unused_asm.py b/tools/find_unused_asm.py
deleted file mode 100644
index 92fde245c1..0000000000
--- a/tools/find_unused_asm.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
-Use as `python tools/find_unused_asm.py | xargs rm`
-"""
-
-import inotify.adapters
-from inotify.constants import IN_OPEN
-from pathlib import Path
-import subprocess
-from sys import stderr
-
-asm_files = set(Path('include/').glob('**/*.s'))
-
-stderr.write('==> clean\n')
-subprocess.run(['make', 'clean'], stdout=subprocess.DEVNULL)
-
-stderr.write('==> set up watches\n')
-ino = inotify.adapters.Inotify()
-for p in asm_files:
- ino.add_watch(str(p), mask=IN_OPEN)
-
-stderr.write('==> run make\n')
-subprocess.run(['make', '-j'], stdout=subprocess.DEVNULL)
-
-opened_paths = set()
-for evt in ino.event_gen(timeout_s=1):
- if evt:
- (header, type_names, path, filename) = evt
- opened_paths.add(Path(path))
-
-unused_asm = asm_files - opened_paths
-for p in unused_asm:
- print(str(p)) \ No newline at end of file
diff --git a/tools/lcf.py b/tools/lcf.py
index 8a1257109c..028dc916f6 100644
--- a/tools/lcf.py
+++ b/tools/lcf.py
@@ -1,31 +1,40 @@
-
"""
lcf.py
Generates the .lcf file used for the linker. This will auto force actives missing functions and data
-and apply some fixes with makes it easier to decompile.
+and apply some fixes which makes it easier to decompile.
"""
import importlib
-import click
-from libdol2asm import settings
-import libelf
-import libar
-from pathlib import Path
import io
import sys
-import os
-VERSION = "1.0"
+from pathlib import Path
+
+try:
+ import click
+ import libelf
+ import libar
+ import dol2asm_settings
+except ImportError as e:
+ MISSING_PREREQUISITES = (
+ f"Missing prerequisite python module {e}.\n"
+ f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites."
+ )
-# laod the symbol definition file for main.dol
-sys.path.append('defs')
+ print(MISSING_PREREQUISITES, file=sys.stderr)
+ sys.exit(1)
+
+VERSION = "1.0"
+
+# load the symbol definition file for main.dol
+sys.path.append("defs")
def lcf_generate(output_path):
- """ Script for generating .lcf files """
+ """Script for generating .lcf files"""
import module0
@@ -36,11 +45,11 @@ def lcf_generate(output_path):
# load object files from the 'build/o_files', this way we need no list of
# object files in the python code.
- with open("build/o_files", 'r') as content_file:
+ with open("build/o_files", "r") as content_file:
o_files = content_file.read().strip().split(" ")
for o_file in o_files:
- with open(o_file, 'rb') as file:
+ with open(o_file, "rb") as file:
obj = libelf.load_object_from_file(None, o_file, file)
symbols.extend(get_symbols_from_object_file(obj))
@@ -62,7 +71,8 @@ def lcf_generate(output_path):
file.write("\t} > text\n")
file.write(
- "\t_stack_addr = (_f_sbss2 + SIZEOF(.sbss2) + 65536 + 0x7) & ~0x7;\n")
+ "\t_stack_addr = (_f_sbss2 + SIZEOF(.sbss2) + 65536 + 0x7) & ~0x7;\n"
+ )
file.write("\t_stack_end = _f_sbss2 + SIZEOF(.sbss2);\n")
file.write("\t_db_stack_addr = (_stack_addr + 0x2000);\n")
file.write("\t_db_stack_end = _stack_addr;\n")
@@ -80,9 +90,9 @@ def lcf_generate(output_path):
names = base_names - main_names
for name in names:
symbol = module0.SYMBOLS[module0.SYMBOL_NAMES[name]]
- if symbol['type'] == "StringBase": # @stringBase0 is handled below
+ if symbol["type"] == "StringBase": # @stringBase0 is handled below
continue
- if symbol['type'] == "LinkerGenerated": # linker handles these symbols
+ if symbol["type"] == "LinkerGenerated": # linker handles these symbols
continue
file.write(f"\t\"{symbol['label']}\" = 0x{symbol['addr']:08X};\n")
@@ -95,40 +105,40 @@ def lcf_generate(output_path):
# that the @stringBase0 symbol is never used and strip it.
file.write("\t/* @stringBase0 */\n")
for x in module0.SYMBOLS:
- if x['type'] == "StringBase":
- file.write("\t\"%s\" = 0x%08X;\n" % (x['label'], x['addr']))
+ if x["type"] == "StringBase":
+ file.write('\t"%s" = 0x%08X;\n' % (x["label"], x["addr"]))
file.write("}\n")
file.write("\n")
file.write("FORCEACTIVE {\n")
for f in FORCE_ACTIVE:
- file.write("\t\"%s\"\n" % f)
+ file.write('\t"%s"\n' % f)
file.write("\n")
file.write("\t/* unreferenced symbols */\n")
for x in module0.SYMBOLS:
- k = x['label']
- if x['type'] == "StringBase":
+ k = x["label"]
+ if x["type"] == "StringBase":
continue
require_force_active = False
# if the symbol is not reachable from the __start add it as forceactive
- if not x['is_reachable'] or sum(x['r']) == 0:
+ if not x["is_reachable"] or sum(x["r"]) == 0:
require_force_active = True
if require_force_active:
file.write(f"\t\"{x['label']}\"\n")
- if not x['label'] in main_names:
+ if not x["label"] in main_names:
file.write(f"\t\"{x['name']}\"\n")
for x in module0.SYMBOLS:
- if x['type'] == "StringBase":
+ if x["type"] == "StringBase":
continue
- if x['is_reachable']:
- if x['label'] != x['name']:
+ if x["is_reachable"]:
+ if x["label"] != x["name"]:
file.write(f"\t\"{x['name']}\"\n")
for symbol in symbols:
@@ -136,7 +146,7 @@ def lcf_generate(output_path):
continue
if "__template" in symbol.name:
- file.write("\t\"%s\"\n" % (symbol.name))
+ file.write('\t"%s"\n' % (symbol.name))
file.write("\n")
file.write("}\n")
@@ -146,27 +156,20 @@ def lcf_generate(output_path):
def rel_lcf_generate(module_index, output_path):
module = importlib.import_module(f"module{module_index}")
- base = settings.REL_TEMP_LOCATION[module.LIBRARIES[0].split(
- "/")[-1] + ".rel"]
+ base = dol2asm_settings.REL_TEMP_LOCATION[
+ module.LIBRARIES[0].split("/")[-1] + ".rel"
+ ]
# load object files from the 'build/o_files', this way we need no list of
# object files in the python code.
- with open(f"build/M{module_index}_ofiles", 'r') as content_file:
+ with open(f"build/M{module_index}_ofiles", "r") as content_file:
all_files = content_file.read().strip().split(" ")
path = f"build/dolzel2/rel/{module.LIBRARIES[0]}"
- archives = [
- path
- for path in all_files
- if path.endswith(".a")
- ]
+ archives = [path for path in all_files if path.endswith(".a")]
- o_files = [
- path
- for path in all_files
- if path.endswith(".o")
- ]
+ o_files = [path for path in all_files if path.endswith(".o")]
# load symbols from compiled files
symbols = []
@@ -174,7 +177,7 @@ def rel_lcf_generate(module_index, output_path):
symbols.extend(load_archive(archive))
for o_file in o_files:
- with open(o_file, 'rb') as file:
+ with open(o_file, "rb") as file:
obj = libelf.load_object_from_file(None, o_file, file)
symbols.extend(get_symbols_from_object_file(obj))
@@ -186,7 +189,7 @@ def rel_lcf_generate(module_index, output_path):
for name, align in REL_SECTIONS:
file.write(f"\t\t{name} :{{}}\n")
- #file.write("\t\t/DISCARD/ : { *(.dead) }\n")
+ # file.write("\t\t/DISCARD/ : { *(.dead) }\n")
file.write("\t}\n")
@@ -202,13 +205,14 @@ def rel_lcf_generate(module_index, output_path):
names = base_names - main_names
for name in names:
symbol = module.SYMBOLS[module.SYMBOL_NAMES[name]]
- if symbol['type'] == "StringBase": # @stringBase0 is handled below
+ if symbol["type"] == "StringBase": # @stringBase0 is handled below
continue
- if symbol['type'] == "LinkerGenerated": # linker handles these symbols
+ if symbol["type"] == "LinkerGenerated": # linker handles these symbols
continue
file.write(
- f"\t\"{symbol['label']}\" = __rel_base + 0x{symbol['addr'] - base:08X}; /* 0x{symbol['addr']:08X} */\n")
+ f"\t\"{symbol['label']}\" = __rel_base + 0x{symbol['addr'] - base:08X}; /* 0x{symbol['addr']:08X} */\n"
+ )
file.write("\n")
file.write("}\n")
@@ -222,27 +226,27 @@ def rel_lcf_generate(module_index, output_path):
file.write("\t/* unreferenced symbols */\n")
for x in module.SYMBOLS:
- k = x['label']
- if x['type'] == "StringBase":
+ k = x["label"]
+ if x["type"] == "StringBase":
continue
require_force_active = False
# if the symbol is not reachable from the __start add it as forceactive
- if not x['is_reachable'] and not x['static']:
+ if not x["is_reachable"] and not x["static"]:
require_force_active = True
if require_force_active:
file.write(f"\t\"{x['label']}\"\n")
- if not x['label'] in main_names:
+ if not x["label"] in main_names:
file.write(f"\t\"{x['name']}\"\n")
for x in module.SYMBOLS:
- if x['type'] == "StringBase":
+ if x["type"] == "StringBase":
continue
- if x['is_reachable']:
- if x['label'] != x['name'] and x['name']:
+ if x["is_reachable"]:
+ if x["label"] != x["name"] and x["name"]:
file.write(f"\t\"{x['name']}\"\n")
for symbol in symbols:
@@ -250,7 +254,7 @@ def rel_lcf_generate(module_index, output_path):
continue
if "__template" in symbol.name:
- file.write("\t\"%s\"\n" % (symbol.name))
+ file.write('\t"%s"\n' % (symbol.name))
file.write("\n")
file.write("}\n")
@@ -293,7 +297,7 @@ SECTIONS = [
(".sdata2", 0x20),
(".sbss2", 0x20),
(".stack", 0x100),
- #(".dead", 0x100),
+ # (".dead", 0x100),
]
REL_SECTIONS = [
@@ -371,14 +375,28 @@ def lcf():
@lcf.command(name="dol")
-@click.option('--output', '-o', 'output_path', required=False, type=PathPath(file_okay=True, dir_okay=False), default="build/dolzel2/ldscript.lcf")
+@click.option(
+ "--output",
+ "-o",
+ "output_path",
+ required=False,
+ type=PathPath(file_okay=True, dir_okay=False),
+ default="build/dolzel2/ldscript.lcf",
+)
def dol(output_path):
lcf_generate(output_path)
@lcf.command(name="rel")
-@click.option('--output', '-o', 'output_path', required=False, type=PathPath(file_okay=True, dir_okay=False), default="build/dolzel2/ldscript.lcf")
-@click.argument('module', metavar="<MODULE>", nargs=1)
+@click.option(
+ "--output",
+ "-o",
+ "output_path",
+ required=False,
+ type=PathPath(file_okay=True, dir_okay=False),
+ default="build/dolzel2/ldscript.lcf",
+)
+@click.argument("module", metavar="<MODULE>", nargs=1)
def rel(output_path, module):
rel_lcf_generate(module, output_path)
diff --git a/tools/libdol2asm/__init__.py b/tools/libdol2asm/__init__.py
index 816d156ad9..8cb6e5e39b 100644
--- a/tools/libdol2asm/__init__.py
+++ b/tools/libdol2asm/__init__.py
@@ -1,13 +1,9 @@
-from pathlib import Path
-
from . import globals
-from . import split_asm
-
-from . import settings
-
VERSION = globals.VERSION
def split(debug_logging, game_path, lib_path, src_path, asm_path, rel_path, inc_path, mk_gen, cpp_gen, asm_gen, sym_gen, rel_gen, process_count, select_modules, select_tu, select_asm):
+ from . import split_asm
+ from . import settings
splitter = split_asm.Dol2AsmSplitter(debug_logging, game_path, lib_path, src_path, asm_path, rel_path, inc_path, mk_gen, cpp_gen, asm_gen, sym_gen, rel_gen, process_count, select_modules, select_tu, select_asm)
return splitter.main() \ No newline at end of file
diff --git a/tools/libdol2asm/requirements.txt b/tools/libdol2asm/requirements.txt
new file mode 100644
index 0000000000..dd1a81ef03
--- /dev/null
+++ b/tools/libdol2asm/requirements.txt
@@ -0,0 +1,9 @@
+rich
+click
+intervaltree
+yaz0
+numpy
+capstone
+aiofiles
+GitPython
+hexdump
diff --git a/tools/libelf/object.py b/tools/libelf/object.py
index ff09754a0a..f0294432c7 100644
--- a/tools/libelf/object.py
+++ b/tools/libelf/object.py
@@ -33,7 +33,7 @@ class Object:
path: Path = None
executable: bool = False
-def load_object_from_file(path, name, file) -> Object:
+def load_object_from_file(path, name, file, skip_symbols = False, skip_relocations = False) -> Object:
obj = Object()
obj.path = path
obj.name = name
@@ -135,98 +135,100 @@ def load_object_from_file(path, name, file) -> Object:
if section.name:
obj.sections[section.name] = section
- # Find all symbols
- for symtab in obj.sym_sections.values():
- if not symtab.header.sh_link in obj.str_sections:
- raise ElfException("symbol table '%s' is not referenceing a valid string table section (sh_link: %i)" % (
- symtab.name, symtab.header.sh_link))
-
- symtab.object_offset = len(obj.symbols)
- strtab = obj.str_sections[symtab.header.sh_link]
- for i,sym in enumerate(symtab.symbols):
- if i == 0:
- symbol = NullSymbol(sym)
+ if not skip_symbols:
+ # Find all symbols
+ for symtab in obj.sym_sections.values():
+ if not symtab.header.sh_link in obj.str_sections:
+ raise ElfException("symbol table '%s' is not referenceing a valid string table section (sh_link: %i)" % (
+ symtab.name, symtab.header.sh_link))
+
+ symtab.object_offset = len(obj.symbols)
+ strtab = obj.str_sections[symtab.header.sh_link]
+ for i,sym in enumerate(symtab.symbols):
+ if i == 0:
+ symbol = NullSymbol(sym)
+ symbol.object = obj
+ obj.symbols.append(symbol)
+ continue
+
+ name = None
+ if sym.st_name:
+ name = strtab.readString(sym.st_name)
+ symbol = None
+ if sym.st_shndx == elf.SHN_UNDEF:
+ symbol = UndefSymbol(sym, name)
+ elif sym.st_shndx == elf.SHN_ABS:
+ symbol = AbsoluteSymbol(sym, name, sym.st_value)
+ else:
+ if not sym.st_shndx in idx_sections:
+ raise ElfException("symbol '%s' has invalid section-id (st_shndx: %i)" % (name, sym.st_shndx))
+ s = idx_sections[sym.st_shndx]
+ symbol = OffsetSymbol(sym, name, idx_sections[sym.st_shndx], sym.st_value)
+
+ assert symbol
symbol.object = obj
+
+ if symbol.name:
+ obj.symbol_map[symbol.name].append(symbol)
+
obj.symbols.append(symbol)
- continue
-
- name = None
- if sym.st_name:
- name = strtab.readString(sym.st_name)
- symbol = None
- if sym.st_shndx == elf.SHN_UNDEF:
- symbol = UndefSymbol(sym, name)
- elif sym.st_shndx == elf.SHN_ABS:
- symbol = AbsoluteSymbol(sym, name, sym.st_value)
- else:
- if not sym.st_shndx in idx_sections:
- raise ElfException("symbol '%s' has invalid section-id (st_shndx: %i)" % (name, sym.st_shndx))
- s = idx_sections[sym.st_shndx]
- symbol = OffsetSymbol(sym, name, idx_sections[sym.st_shndx], sym.st_value)
-
- assert symbol
- symbol.object = obj
-
- if symbol.name:
- obj.symbol_map[symbol.name].append(symbol)
-
- obj.symbols.append(symbol)
-
- # Find all relocations
- for rela_section in obj.rela_sections.values():
- if not rela_section.header.sh_link in obj.sym_sections:
- raise ElfException("relocation section '%s' is not referenceing a valid symbol table section (sh_link: %i)" % (
- symtab.name, rela_section.header.sh_link))
- if not rela_section.header.sh_info in idx_sections:
- raise ElfException("relocation section '%s' is not referenceing a valid section (sh_info: %i)" % (
- symtab.name, rela_section.header.sh_info))
-
- symtab = obj.sym_sections[rela_section.header.sh_link]
- modify = idx_sections[rela_section.header.sh_info]
-
- section_relocations = []
- for rela in rela_section.relocations:
- type = elf.R_TYPE(rela.r_info)
- sym_id = elf.R_SYM(rela.r_info)
- if not type in RELOCATION_NAMES:
- raise ElfException("unsupported relocation type: 0x%02X (in '%s')" % (type, path))
-
- if sym_id < 0 or sym_id >= len(symtab.symbols):
- # report warning?
- # main.elf will generate relocation sections with invalid symbol indices
- # raise ElfException("invalid symbol index: %i (%i symbols)" % (sym_id, len(symtab.symbols)))
- continue
- symbol = obj.symbols[symtab.object_offset + sym_id]
-
- relocation = None
- if type == 1:
- relocation = R_PPC_ADDR32(type, symbol, modify, rela.r_offset, rela.r_addend)
- elif type == 3:
- relocation = R_PPC_ADDR16(type, symbol, modify, rela.r_offset, rela.r_addend)
- elif type == 4:
- relocation = R_PPC_ADDR16_LO(type, symbol, modify, rela.r_offset, rela.r_addend)
- elif type == 5:
- relocation = R_PPC_ADDR16_HI(type, symbol, modify, rela.r_offset, rela.r_addend)
- elif type == 6:
- relocation = R_PPC_ADDR16_HA(type, symbol, modify, rela.r_offset, rela.r_addend)
- elif type == 10:
- relocation = R_PPC_REL24(type, symbol, modify, rela.r_offset, rela.r_addend)
- elif type == 11:
- relocation = R_PPC_REL14(type, symbol, modify, rela.r_offset, rela.r_addend)
- elif type == 109:
- relocation = R_PPC_EMB_SDA21(type, symbol, modify, rela.r_offset, rela.r_addend)
- else:
- print("unsupported relocation type: 0x%02X \"%s\" (in '%s')" % (type, RELOCATION_NAMES[type], path), file = sys.stderr)
- continue
-
- assert relocation
- section_relocations.append(relocation)
- obj.relocations.append(relocation)
-
- obj.section_relocations.append((rela_section.name, section_relocations))
+
+ if not skip_relocations:
+ # Find all relocations
+ for rela_section in obj.rela_sections.values():
+ if not rela_section.header.sh_link in obj.sym_sections:
+ raise ElfException("relocation section '%s' is not referenceing a valid symbol table section (sh_link: %i)" % (
+ symtab.name, rela_section.header.sh_link))
+ if not rela_section.header.sh_info in idx_sections:
+ raise ElfException("relocation section '%s' is not referenceing a valid section (sh_info: %i)" % (
+ symtab.name, rela_section.header.sh_info))
+
+ symtab = obj.sym_sections[rela_section.header.sh_link]
+ modify = idx_sections[rela_section.header.sh_info]
+
+ section_relocations = []
+ for rela in rela_section.relocations:
+ type = elf.R_TYPE(rela.r_info)
+ sym_id = elf.R_SYM(rela.r_info)
+ if not type in RELOCATION_NAMES:
+ raise ElfException("unsupported relocation type: 0x%02X (in '%s')" % (type, path))
+
+ if sym_id < 0 or sym_id >= len(symtab.symbols):
+ # report warning?
+ # main.elf will generate relocation sections with invalid symbol indices
+ # raise ElfException("invalid symbol index: %i (%i symbols)" % (sym_id, len(symtab.symbols)))
+ continue
+ symbol = obj.symbols[symtab.object_offset + sym_id]
+
+ relocation = None
+ if type == 1:
+ relocation = R_PPC_ADDR32(type, symbol, modify, rela.r_offset, rela.r_addend)
+ elif type == 3:
+ relocation = R_PPC_ADDR16(type, symbol, modify, rela.r_offset, rela.r_addend)
+ elif type == 4:
+ relocation = R_PPC_ADDR16_LO(type, symbol, modify, rela.r_offset, rela.r_addend)
+ elif type == 5:
+ relocation = R_PPC_ADDR16_HI(type, symbol, modify, rela.r_offset, rela.r_addend)
+ elif type == 6:
+ relocation = R_PPC_ADDR16_HA(type, symbol, modify, rela.r_offset, rela.r_addend)
+ elif type == 10:
+ relocation = R_PPC_REL24(type, symbol, modify, rela.r_offset, rela.r_addend)
+ elif type == 11:
+ relocation = R_PPC_REL14(type, symbol, modify, rela.r_offset, rela.r_addend)
+ elif type == 109:
+ relocation = R_PPC_EMB_SDA21(type, symbol, modify, rela.r_offset, rela.r_addend)
+ else:
+ print("unsupported relocation type: 0x%02X \"%s\" (in '%s')" % (type, RELOCATION_NAMES[type], path), file = sys.stderr)
+ continue
+
+ assert relocation
+ section_relocations.append(relocation)
+ obj.relocations.append(relocation)
+
+ obj.section_relocations.append((rela_section.name, section_relocations))
return obj
-def load_object_from_path(path) -> Object:
+def load_object_from_path(path, skip_symbols = False, skip_relocations = False) -> Object:
with open(path, 'rb') as file:
- return load_object_from_file(path, path.parts[-1], file) \ No newline at end of file
+ return load_object_from_file(path, path.parts[-1], file, skip_symbols, skip_relocations) \ No newline at end of file
diff --git a/tools/makerel.py b/tools/makerel.py
index 779018b74b..763ce1726d 100644
--- a/tools/makerel.py
+++ b/tools/makerel.py
@@ -4,23 +4,31 @@ makerel.py - Generate .rel files from .plf files and a static binary
"""
-import click
import sys
-import rich
-import logging
import glob
import os
-import libelf
-import librel
-import yaz0
import traceback
from pathlib import Path
-from collections import defaultdict
from dataclasses import dataclass, field
from typing import List, Set, Tuple, Dict
-from rich.logging import RichHandler
-from rich.console import Console
+
+try:
+ import libelf
+ import librel
+ import click
+ import logging
+
+ from rich.logging import RichHandler
+ from rich.console import Console
+except ImportError as e:
+ MISSING_PREREQUISITES = (
+ f"Missing prerequisite python module {e}.\n"
+ f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites."
+ )
+
+ print(MISSING_PREREQUISITES, file=sys.stderr)
+ sys.exit(1)
VERSION = "1.0"
CONSOLE = Console()
@@ -29,21 +37,13 @@ logging.basicConfig(
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
- handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)]
+ handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)],
)
LOG = logging.getLogger("rich")
LOG.setLevel(logging.INFO)
-SECTION_MASK = {
- ".init",
- ".text",
- ".ctors",
- ".dtors",
- ".rodata",
- ".data",
- ".bss"
-}
+SECTION_MASK = {".init", ".text", ".ctors", ".dtors", ".rodata", ".data", ".bss"}
REL_SECTION_MASK = {
".rela.init",
@@ -55,17 +55,19 @@ REL_SECTION_MASK = {
".rela.bss",
}
+
@click.group()
@click.version_option(VERSION)
def makerel():
pass
+
@makerel.command(name="unresolved")
-@click.option('--debug/--no-debug')
+@click.option("--debug/--no-debug")
@click.option("--output", "-o", default="forceactive.txt", required=True)
-@click.argument("str_paths", metavar='<ELFs>', nargs=-1)
+@click.argument("str_paths", metavar="<ELFs>", nargs=-1)
def unresolved(debug, output, str_paths):
- """ Generate a list of symbols which must be in the static executable (and other RELs). """
+ """Generate a list of symbols which must be in the static executable (and other RELs)."""
if debug:
LOG.setLevel(logging.DEBUG)
@@ -73,8 +75,7 @@ def unresolved(debug, output, str_paths):
static, plfs = load_elfs(str_paths)
if static:
- LOG.error(
- f"unresolved does not handle executable files '{static.path}'")
+ LOG.error(f"unresolved does not handle executable files '{static.path}'")
sys.exit(1)
undef_symbols = set()
@@ -97,15 +98,21 @@ def unresolved(debug, output, str_paths):
@makerel.command(name="build")
-@click.option('--debug/--no-debug')
-@click.option('--yaz0', '-y', 'compress_yaz0')
-@click.option("--id-offset", '-i', 'rel_id_offset', default=1)
-@click.option("--spoof-path", '-q', 'spoof_path', default="D:\\zeldaGC_USA\\dolzel2\\bin\\Final\\")
-@click.option("--string-table", '-s', 'string_path', required=True)
-@click.option("--symbols", default="ELF", type=click.Choice(["ELF", "DEFS"], case_sensitive=False))
-@click.argument("str_paths", metavar='<ELFs>', nargs=-1)
-def build(debug, symbols, str_paths, rel_id_offset, compress_yaz0, spoof_path, string_path):
- """ Build RELs files from a list of plfs files. """
+@click.option("--debug/--no-debug")
+@click.option("--yaz0", "-y", "compress_yaz0")
+@click.option("--id-offset", "-i", "rel_id_offset", default=1)
+@click.option(
+ "--spoof-path", "-q", "spoof_path", default="D:\\zeldaGC_USA\\dolzel2\\bin\\Final\\"
+)
+@click.option("--string-table", "-s", "string_path", required=True)
+@click.option(
+ "--symbols", default="ELF", type=click.Choice(["ELF", "DEFS"], case_sensitive=False)
+)
+@click.argument("str_paths", metavar="<ELFs>", nargs=-1)
+def build(
+ debug, symbols, str_paths, rel_id_offset, compress_yaz0, spoof_path, string_path
+):
+ """Build RELs files from a list of plfs files."""
if debug:
LOG.setLevel(logging.DEBUG)
@@ -115,7 +122,7 @@ def build(debug, symbols, str_paths, rel_id_offset, compress_yaz0, spoof_path, s
LOG.error(f"static executable ('main.elf') expected")
sys.exit(1)
- #
+ #
id = rel_id_offset
elfs = []
for plf in plfs:
@@ -123,7 +130,7 @@ def build(debug, symbols, str_paths, rel_id_offset, compress_yaz0, spoof_path, s
rel = IndexedElf(0, plf)
else:
rel = IndexedElf(id, plf)
- id += 1
+ id += 1
elfs.append(rel)
# sort relocations
@@ -132,7 +139,7 @@ def build(debug, symbols, str_paths, rel_id_offset, compress_yaz0, spoof_path, s
elf._unresolved = elf.plf.symbol_map["_unresolved"][0]
for _, relocations in elf.plf.section_relocations:
- relocations.sort(key=lambda r: r.offset)
+ relocations.sort(key=lambda r: r.offset)
# symbol table
symbol_table = dict()
@@ -155,17 +162,30 @@ def build(debug, symbols, str_paths, rel_id_offset, compress_yaz0, spoof_path, s
string_list.write()
+
def apply_rel24_relocation(relocation, section, symbol):
if not symbol or not isinstance(symbol, libelf.OffsetSymbol):
return False
try:
- if librel.apply_relocation(relocation.type, 0, section.data, 0, relocation.offset, symbol.offset, relocation.addend):
+ if librel.apply_relocation(
+ relocation.type,
+ 0,
+ section.data,
+ 0,
+ relocation.offset,
+ symbol.offset,
+ relocation.addend,
+ ):
return True
except librel.RELRelocationException as e:
LOG.error(f"applying relocation failed!")
- LOG.error(f"relocation: {librel.RELOCATION_NAMES[relocation.type]} {relocation.offset:04X}")
- LOG.error(f"section: {section.header.sh_addr:08X} {section.header.sh_size:04X} {section.name}")
+ LOG.error(
+ f"relocation: {librel.RELOCATION_NAMES[relocation.type]} {relocation.offset:04X}"
+ )
+ LOG.error(
+ f"section: {section.header.sh_addr:08X} {section.header.sh_size:04X} {section.name}"
+ )
LOG.error(f"symbol: {symbol.offset:08X} {symbol.name}+0x{relocation.addend:X}")
LOG.error(e)
CONSOLE.print_exception()
@@ -175,6 +195,7 @@ def apply_rel24_relocation(relocation, section, symbol):
return False
+
@dataclass
class StringList:
output_path: str
@@ -189,9 +210,9 @@ class StringList:
self.data += name
return offset, len(name)
- def write(self):
+ def write(self):
if len(self.data) > 0:
- with open(self.output_path, 'w') as file:
+ with open(self.output_path, "w") as file:
file.write(self.data)
@@ -200,7 +221,7 @@ class ImpTable:
id: int
last_section: int
section_offset: int = 0
- relocations: List[Tuple[int,int,int,int]] = field(default_factory=list)
+ relocations: List[Tuple[int, int, int, int]] = field(default_factory=list)
rel_offset: int = 0
def section(self, section_id):
@@ -224,7 +245,7 @@ class IndexedElf:
plf: libelf.Object
_unresolved: libelf.Symbol = None
- imp_tables: Dict[int,ImpTable] = field(default_factory=dict)
+ imp_tables: Dict[int, ImpTable] = field(default_factory=dict)
imp_table_order: List[int] = field(default_factory=list)
complete_relocations: Set[libelf.Relocation] = field(default_factory=set)
@@ -250,8 +271,7 @@ class IndexedElf:
if replace_symbol:
my_symbol = replace_symbol
-
- #LOG.info(f"relocation for: {my_symbol.name} ({type(my_symbol).__name__})")
+ # LOG.info(f"relocation for: {my_symbol.name} ({type(my_symbol).__name__})")
other = None
if isinstance(my_symbol, libelf.UndefSymbol):
if my_symbol.name in symbol_table:
@@ -291,14 +311,20 @@ class IndexedElf:
found_section = None
for section in other.plf.sections.values():
- if ext_symbol.address >= section.header.sh_addr and ext_symbol.address < section.header.sh_addr + section.header.sh_size:
+ if (
+ ext_symbol.address >= section.header.sh_addr
+ and ext_symbol.address
+ < section.header.sh_addr + section.header.sh_size
+ ):
found_section = section
break
if found_section:
ext_symbol.section = found_section
else:
- LOG.error(f"error no-section provided for relocation of: {my_symbol.name} ({type(my_symbol).__name__}) ({self.plf.name} <- {other.plf.name})")
+ LOG.error(
+ f"error no-section provided for relocation of: {my_symbol.name} ({type(my_symbol).__name__}) ({self.plf.name} <- {other.plf.name})"
+ )
LOG.error(vars(relocation))
LOG.error(ext_symbol)
k = vars(ext_symbol)
@@ -317,8 +343,8 @@ class IndexedElf:
section_id = ext_symbol.section.header.sh_info
if section_id == 0:
section_id = ext_symbol.section.header.id
-
- table.relocation(relative_offset, relocation.type, section_id, addend)
+
+ table.relocation(relative_offset, relocation.type, section_id, addend)
self.complete_relocations.add(relocation)
return True
return False
@@ -333,26 +359,30 @@ class IndexedElf:
LOG.error(f"relocation failed: {name:<14} {relocation.symbol.name}")
sys.exit(1)
+
def align_next(offset, alignment):
- return (offset - 1 + alignment) & ~(alignment - 1)
-
-def write_rel(path: Path,
- id: int,
- align: int,
- bss_align: int,
- bss_size: int,
- name_offset: int,
- name_size: int,
- prolog: libelf.Symbol,
- epilog: libelf.Symbol,
- unresolved: libelf.Symbol,
- sections: List[librel.Section],
- imp_tables: List[ImpTable]):
+ return (offset - 1 + alignment) & ~(alignment - 1)
+
+
+def write_rel(
+ path: Path,
+ id: int,
+ align: int,
+ bss_align: int,
+ bss_size: int,
+ name_offset: int,
+ name_size: int,
+ prolog: libelf.Symbol,
+ epilog: libelf.Symbol,
+ unresolved: libelf.Symbol,
+ sections: List[librel.Section],
+ imp_tables: List[ImpTable],
+):
output = librel.REL()
output.index = id
output.numSections = len(sections)
- output.sectionInfoOffset = 0x4C # for version 3
+ output.sectionInfoOffset = 0x4C # for version 3
output.nameOffset = name_offset
output.nameSize = name_size
output.version = 3
@@ -382,7 +412,7 @@ def write_rel(path: Path,
assert output.version >= 3
- with path.open('wb') as file:
+ with path.open("wb") as file:
librel.write_header(file, output)
sections_offset = file.tell()
@@ -396,7 +426,7 @@ def write_rel(path: Path,
if section.data:
padding = section.offset - file.tell()
if padding > 0:
- file.write(b'\x00' * padding)
+ file.write(b"\x00" * padding)
assert section.offset == file.tell()
librel.write_section_data(file, section)
@@ -404,9 +434,9 @@ def write_rel(path: Path,
output.impOffset = file.tell()
output.impSize = len(imp_tables) * 0x8
- file.write(b'\xFF' * output.impSize)
+ file.write(b"\xFF" * output.impSize)
- output.fixSize = file.tell()
+ output.fixSize = file.tell()
output.relOffset = file.tell()
rel_offset = output.relOffset
for table in imp_tables:
@@ -425,7 +455,6 @@ def write_rel(path: Path,
librel.write_header(file, output)
-
def write_rel_from_elf(elf: IndexedElf, string_list: StringList, compress_yaz0: bool):
assert elf.id != 0
@@ -442,7 +471,7 @@ def write_rel_from_elf(elf: IndexedElf, string_list: StringList, compress_yaz0:
table.end()
# count sections
- section_count = 1 # null section
+ section_count = 1 # null section
for elf_section in elf.plf.sections.values():
if elf_section.name == ".dead" or elf_section.name == ".rela.dead":
continue
@@ -460,7 +489,9 @@ def write_rel_from_elf(elf: IndexedElf, string_list: StringList, compress_yaz0:
if not elf_section.name in SECTION_MASK:
continue
- section = librel.Section(elf_section.header.id, 0, False, False, elf_section.header.sh_size)
+ section = librel.Section(
+ elf_section.header.id, 0, False, False, elf_section.header.sh_size
+ )
if elf_section.header.sh_type == libelf.SHT_NOBITS:
if elf_section.header.sh_addralign >= 1:
if elf_section.header.sh_addralign >= bss_align:
@@ -474,12 +505,12 @@ def write_rel_from_elf(elf: IndexedElf, string_list: StringList, compress_yaz0:
if elf_section.header.sh_addralign >= align:
align = elf_section.header.sh_addralign
-
+
section.offset = offset
section.data = elf_section.data
if (elf_section.header.sh_flags & libelf.SHF_EXECINSTR) != 0:
- section.executable_flag = True
-
+ section.executable_flag = True
+
offset += section.length
sections.append(section)
@@ -510,7 +541,21 @@ def write_rel_from_elf(elf: IndexedElf, string_list: StringList, compress_yaz0:
tables.append(elf.imp_tables[0])
# write the rel files
- write_rel(path, elf.id, align, bss_align, bss_size, name_offset, name_size, prolog, epilog, unresolved, sections, tables)
+ write_rel(
+ path,
+ elf.id,
+ align,
+ bss_align,
+ bss_size,
+ name_offset,
+ name_size,
+ prolog,
+ epilog,
+ unresolved,
+ sections,
+ tables,
+ )
+
def load_elfs(str_paths):
static = None
@@ -535,7 +580,6 @@ def load_elfs(str_paths):
LOG.error(f"error: '{path}'")
LOG.error(e)
-
return static, plfs
diff --git a/tools/postprocess.py b/tools/postprocess.py
deleted file mode 100644
index b486ab218d..0000000000
--- a/tools/postprocess.py
+++ /dev/null
@@ -1,319 +0,0 @@
-#!/usr/bin/env python3
-
-BANNER = """
-# This script is the culmination of three patches supporting decompilation
-# with the CodeWarrior compiler.
-# - riidefi, 2020
-#
-# postprocess.py [args] file
-#
-# 1) Certain versions have a bug where the ctor alignment is ignored and set incorrectly.
-# This option is enabled with -fctor-realign, and disabled by default with -fno-ctor-realign
-#
-# 2) Certain C++ symbols cannot be assembled normally.
-# To support the buildsystem, a simple substitution system has been devised
-#
-# ?<ID> -> CHAR
-#
-# IDs (all irregular symbols in mangled names):
-# 0: <
-# 1: >
-# 2: @
-# 3: \\
-# 4: ,
-# 5: -
-#
-# This option is enabled with -fsymbol-fixup, and disabled by default with -fno-symbol-fixup
-#
-# 3) CodeWarrior versions below 2.3 used a different scheduler model.
-# The script can currently adjust function epilogues with the old_stack option.
-# -fprologue-fixup=[default=none, none, old_stack]
-"""
-
-import struct
-
-# Substitutions
-substitutions = (
- ('<', '_SUB_0'),
- ('>', '_SUB_1'),
- ('@', '_SUB_2'),
- ('\\', '_SUB_3'),
- (',', '_SUB_4'),
- ('-', '_SUB_5')
-)
-
-def format(symbol):
- for sub in substitutions:
- symbol = symbol.replace(sub[0], sub[1])
-
- return symbol
-
-def decodeformat(symbol):
- for sub in substitutions:
- symbol = symbol.replace(sub[1], sub[0])
-
- return symbol
-
-# Stream utilities
-
-def read_u8(f):
- return struct.unpack("B", f.read(1))[0]
-
-def read_u32(f):
- return struct.unpack(">I", f.read(4))[0]
-
-def read_u16(f):
- return struct.unpack(">H", f.read(2))[0]
-
-def write_u32(f, val):
- f.write(struct.pack(">I", val))
-
-class ToReplace:
- def __init__(self, position, dest, src_size):
- self.position = position # Where in file
- self.dest = dest # String to patch
- self.src_size = src_size # Pad rest with zeroes
-
- # print("To replace: %s %s %s" % (self.position, self.dest, self.src_size))
-
-def read_string(f):
- tmp = ""
- c = 0xff
- while c != 0x00:
- c = read_u8(f)
- if c != 0:
- tmp += chr(c)
- return tmp
-
-def ctor_realign(f, ofsSecHeader, nSecHeader, idxSegNameSeg):
- patch_align_ofs = []
-
- for i in range(nSecHeader):
- f.seek(ofsSecHeader + i * 0x28)
- ofsname = read_u32(f)
- if not ofsname: continue
-
- back = f.tell()
-
- f.seek(ofsSecHeader + (idxSegNameSeg * 0x28) + 0x10)
- ofsShST = read_u32(f)
- f.seek(ofsShST + ofsname)
- name = read_string(f)
- if name == ".ctors" or name == ".dtors":
- patch_align_ofs.append(ofsSecHeader + i * 0x28 + 0x20)
-
- f.seek(back)
-
- return patch_align_ofs
-
-SHT_PROGBITS = 1
-SHT_STRTAB = 3
-
-def impl_postprocess_elf(f, do_ctor_realign, do_old_stack, do_symbol_fixup):
- result = []
-
- f.seek(0x20)
- ofsSecHeader = read_u32(f)
- f.seek(0x30)
- nSecHeader = read_u16(f)
- idxSegNameSeg = read_u16(f)
- secF = True # First instance the section names
-
- # Header: 0x32:
- patch_align_ofs = []
-
- if do_ctor_realign:
- patch_align_ofs = ctor_realign(f, ofsSecHeader, nSecHeader, idxSegNameSeg)
-
- for i in range(nSecHeader):
- f.seek(ofsSecHeader + i * 0x28)
- sh_name = read_u32(f)
- sh_type = read_u32(f)
-
- if sh_type == SHT_STRTAB and do_symbol_fixup:
- if not secF:
- continue
- secF = False
-
- f.seek(ofsSecHeader + i * 0x28 + 0x10)
- ofs = read_u32(f)
- size = read_u32(f)
-
- f.seek(ofs)
- string = ""
- str_spos = ofs
- for i in range(ofs, ofs+size):
- c = read_u8(f)
- if c == 0:
- if len(string):
- fixed = decodeformat(string)
- if fixed != string:
- result.append(ToReplace(str_spos, fixed, len(string)))
- string = ""
- str_spos = i+1
- else:
- string += chr(c)
- else:
- f.seek(ofsSecHeader + (idxSegNameSeg * 0x28) + 0x10)
- ofsShST = read_u32(f)
- f.seek(ofsShST + sh_name)
- name = read_string(f)
-
- if name == ".text" and do_old_stack:
- f.seek(ofsSecHeader + i * 0x28 + 0x10)
- ofs = read_u32(f)
- size = read_u32(f)
-
- # We assume
- # 1) Only instructions are in the .text section
- # 2) These instructions are 4-byte aligned
- assert ofs != 0
- assert ofs % 4 == 0
- assert size % 4 == 0
-
- f.seek(ofs)
-
- mtlr_pos = 0
-
- # (mtlr position, blr position)
- epilogues = []
-
- for _ in range(ofs, ofs+size, 4):
- it = f.tell()
- instr = read_u32(f)
-
- # Skip padding
- if instr == 0: continue
-
- # Call analysis is not actually required
- # No mtlr will exist without a blr; mtctr/bctr* is used for dynamic dispatch
-
- # FUN_A:
- # li r3, 0
- # blr <---- No mtlr, move onto the next function
- # FUN_B:
- # ; complex function, stack manip
- # mtlr r0 <---- Expect a blr
- # addi r1, r1, 24
- # blr <---- Confirm patch above
-
- # mtlr alias for mtspr
- if instr == 0x7C0803A6:
- assert mtlr_pos == 0
- mtlr_pos = it
- # blr
- elif instr == 0x4E800020:
- if mtlr_pos:
- epilogues.append((mtlr_pos, it))
- mtlr_pos = 0
-
-
- # Check for a lone mtlr
- assert mtlr_pos == 0
-
- # Reunify mtlr/blr instructions, shifting intermediary instructions up
- for mtlr_pos, blr_pos in epilogues:
- # Check if we need to do anything
- if mtlr_pos + 4 == blr_pos: continue
-
- # As the processor can only hold 6 instructions at once in the pipeline,
- # it's unlikely for the mtlr be shifted up more instructions than that--usually,
- # only one:
- # mtlr r0
- # addi r1, r1, 24
- # blr
- assert blr_pos - 4 > mtlr_pos
- assert blr_pos - mtlr_pos <= 6 * 4
-
- print("Patching old epilogue: %s %s" % (mtlr_pos, blr_pos))
-
- f.seek(mtlr_pos)
- mtlr = read_u32(f)
-
- for it in range(mtlr_pos, blr_pos - 4, 4):
- f.seek(it + 4)
- next_instr = read_u32(f)
- f.seek(it)
- write_u32(f, next_instr)
-
- f.seek(blr_pos - 4)
- write_u32(f, mtlr)
-
- return (result, patch_align_ofs)
-
-def postprocess_elf(f, do_ctor_realign, do_old_stack, do_symbol_fixup):
- patches = impl_postprocess_elf(f, do_ctor_realign, do_old_stack, do_symbol_fixup)
-
- f.seek(0)
- source_bytes = list(f.read())
- for patch in patches[0]:
- assert len(patch.dest) <= patch.src_size
- for j in range(patch.src_size):
- if j >= len(patch.dest):
- c = 0
- else:
- c = ord(patch.dest[j])
- source_bytes[patch.position + j] = c
-
- # Patch ctor align
- nP = 0
- for p in patches[1]:
- print("Patching ctors")
- source_bytes[p + 0] = 0
- source_bytes[p + 1] = 0
- source_bytes[p + 2] = 0
- source_bytes[p + 3] = 4
- nP += 1
- if nP > 1:
- print("Patched ctors + dtors")
-
- f.seek(0)
- f.write(bytes(source_bytes))
-
-def frontend(args):
- inplace = ""
- do_ctor_realign = False
- do_old_stack = False
- do_symbol_fixup = False
-
- for arg in args:
- if arg.startswith('-f'):
- negated = False
- if arg.startswith('-fno-'):
- negated = True
- arg = arg[len('-fno-'):]
- else:
- arg = arg[len('-f'):]
-
- if arg == 'ctor_realign':
- do_ctor_realign = not negated
- elif arg == 'symbol-fixup':
- do_symbol_fixup = not negated
- elif arg.startswith('prologue-fixup='):
- do_old_stack = arg[len('prologue-fixup='):] == 'old_stack'
- else:
- print("Unknown argument: %s" % arg)
- elif arg.startswith('-'):
- print("Unknown argument: %s. Perhaps you meant -f%s?" % (arg, arg))
- else:
- if inplace:
- print("Cannot process %s. Only one source file may be specified." % arg)
- else:
- inplace = arg
-
- if not inplace:
- print("A file must be specified!")
- return
-
- try:
- postprocess_elf(open(inplace, 'rb+'), do_ctor_realign, do_old_stack, do_symbol_fixup)
- except FileNotFoundError:
- print("Cannot open file %s" % inplace)
-
-if __name__ == "__main__":
- import sys
-
- if len(sys.argv) < 2:
- print(BANNER)
- else:
- frontend(sys.argv[1:])
diff --git a/tools/rel.py b/tools/rel.py
index 12c6f8e7e2..22beadaad4 100644
--- a/tools/rel.py
+++ b/tools/rel.py
@@ -1,24 +1,30 @@
"""
-rel.py - Tool for displaying information in .rel files
+rel.py - Tool for extracting information from .rel files
"""
-import click
import sys
-import rich
-import logging
-import glob
-import librel
import struct
-import dataclasses
-import hexdump
-from libdol2asm import settings
from pathlib import Path
-from collections import defaultdict
-from rich.logging import RichHandler
-from rich.console import Console
+
+try:
+ import click
+ import logging
+ import librel
+
+ from libdol2asm import settings
+ from rich.logging import RichHandler
+ from rich.console import Console
+except ImportError as e:
+ MISSING_PREREQUISITES = (
+ f"Missing prerequisite python module {e}.\n"
+ f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites."
+ )
+
+ print(MISSING_PREREQUISITES, file=sys.stderr)
+ sys.exit(1)
VERSION = "1.0"
CONSOLE = Console()
@@ -27,26 +33,43 @@ logging.basicConfig(
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
- handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)]
+ handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)],
)
LOG = logging.getLogger("rich")
LOG.setLevel(logging.INFO)
+
@click.group()
@click.version_option(VERSION)
def rel():
pass
+
@rel.command(name="info")
-@click.option('--debug/--no-debug')
-@click.option('--header', '-t', 'dump_header', is_flag=True, default=False)
-@click.option('--sections', '-s', 'dump_sections', is_flag=True, default=False)
-@click.option('--data', '-d', 'dump_data', is_flag=True, default=False)
-@click.option('--relocations', '-r', 'dump_relocation', is_flag=True, default=False)
-@click.option('--imp', '-i', 'dump_imp', is_flag=True, default=False)
-@click.argument("rel_path", metavar='<REL>', nargs=1, type=click.Path(exists=True,file_okay=True,dir_okay=False))
-def rel_info(debug, rel_path, dump_header, dump_sections, dump_data, dump_relocation, dump_imp):
+@click.option("--debug/--no-debug")
+@click.option("--header", "-t", "dump_header", is_flag=True, default=False)
+@click.option("--sections", "-s", "dump_sections", is_flag=True, default=False)
+@click.option("--data", "-d", "dump_data", is_flag=True, default=False)
+@click.option("--relocations", "-r", "dump_relocation", is_flag=True, default=False)
+@click.option("--imp", "-i", "dump_imp", is_flag=True, default=False)
+@click.option("--all", "-a", "dump_all", is_flag=True, default=False)
+@click.argument(
+ "rel_path",
+ metavar="<REL>",
+ nargs=1,
+ type=click.Path(exists=True, file_okay=True, dir_okay=False),
+)
+def rel_info(
+ debug,
+ rel_path,
+ dump_header,
+ dump_sections,
+ dump_data,
+ dump_relocation,
+ dump_imp,
+ dump_all,
+):
if debug:
LOG.setLevel(logging.DEBUG)
@@ -55,10 +78,20 @@ def rel_info(debug, rel_path, dump_header, dump_sections, dump_data, dump_reloca
LOG.error(f"File not found: '{path}'")
sys.exit(1)
- with open(path, 'rb') as file:
+ with open(path, "rb") as file:
buffer = file.read()
rel = librel.read(buffer)
+ if dump_all:
+ dump_header = True
+ dump_sections = True
+ dump_data = True
+ dump_relocation = True
+ dump_imp = True
+
+ if not (dump_header or dump_sections or dump_data or dump_relocation or dump_imp):
+ CONSOLE.print("no dump options specified")
+
if dump_header:
CONSOLE.print(f"Header:")
CONSOLE.print(f"\tindex: {rel.index}")
@@ -76,32 +109,42 @@ def rel_info(debug, rel_path, dump_header, dump_sections, dump_data, dump_reloca
CONSOLE.print(f"\trel offset: 0x{rel.relOffset:04X}")
CONSOLE.print(f"\timp offset: 0x{rel.impOffset:04X}")
CONSOLE.print(f"\timp size: 0x{rel.impSize:04X}")
- CONSOLE.print(f"\t_prolog: 0x{rel.prolog:04X} (section: 0x{rel.prologSection:X})")
- CONSOLE.print(f"\t_epilog: 0x{rel.epilog:04X} (section: 0x{rel.epilogSection:X})")
- CONSOLE.print(f"\t_unresolved: 0x{rel.unresolved:04X} (section: 0x{rel.unresolvedSection:X})")
+ CONSOLE.print(
+ f"\t_prolog: 0x{rel.prolog:04X} (section: 0x{rel.prologSection:X})"
+ )
+ CONSOLE.print(
+ f"\t_epilog: 0x{rel.epilog:04X} (section: 0x{rel.epilogSection:X})"
+ )
+ CONSOLE.print(
+ f"\t_unresolved: 0x{rel.unresolved:04X} (section: 0x{rel.unresolvedSection:X})"
+ )
if dump_sections:
CONSOLE.print(f"Sections:")
- for i,section in enumerate(rel.sections):
- CONSOLE.print(f"\t#{i:<2} offset: 0x{section.offset:08X}, length: 0x{section.length:04X}, unknown flag: {section.unknown_flag}, executable flag: {section.executable_flag}")
+ for i, section in enumerate(rel.sections):
+ CONSOLE.print(
+ f"\t#{i:<2} offset: 0x{section.offset:08X}, length: 0x{section.length:04X}, unknown flag: {section.unknown_flag}, executable flag: {section.executable_flag}"
+ )
if dump_imp:
CONSOLE.print(f"Imp Table:")
- imp_buffer = rel.data[rel.impOffset:]
+ imp_buffer = rel.data[rel.impOffset :]
for i in range(rel.impSize // 8):
imp_offset = 0x8 * i
- module_id, rel_offset = struct.unpack('>II', imp_buffer[imp_offset:][:0x8])
- CONSOLE.print(f"\t#{i:<2} module: {module_id:>4}, offset: 0x{rel_offset:04X}")
+ module_id, rel_offset = struct.unpack(">II", imp_buffer[imp_offset:][:0x8])
+ CONSOLE.print(
+ f"\t#{i:<2} module: {module_id:>4}, offset: 0x{rel_offset:04X}"
+ )
if dump_relocation:
CONSOLE.print(f"Relocations:")
-
- imp_buffer = rel.data[rel.impOffset:]
+
+ imp_buffer = rel.data[rel.impOffset :]
for i in range(rel.impSize // 8):
imp_offset = 0x8 * i
- module_id, rel_offset = struct.unpack('>II', imp_buffer[imp_offset:][:0x8])
+ module_id, rel_offset = struct.unpack(">II", imp_buffer[imp_offset:][:0x8])
CONSOLE.print(f"\t[ module: {module_id:>4} ]")
section = None
@@ -118,13 +161,15 @@ def rel_info(debug, rel_path, dump_header, dump_sections, dump_data, dump_reloca
base += section.offset - rel.sections[1].offset
extra = f" | {base:08X} {base+relocation.offset + offset:08X}"
- CONSOLE.print(f"\t#{rel_index:<3} {librel.RELOCATION_NAMES[relocation.type]:<20} {relocation.section:>4} 0x{relocation.offset+offset:08X} 0x{relocation.addend:08X}{extra}")
+ CONSOLE.print(
+ f"\t#{rel_index:<3} {librel.RELOCATION_NAMES[relocation.type]:<20} {relocation.section:>4} 0x{relocation.offset+offset:08X} 0x{relocation.addend:08X}{extra}"
+ )
if relocation.type == librel.R_PPC_NONE:
continue
if relocation.type == librel.R_DOLPHIN_NOP:
- # NOP is used to simulate have long offset values, this is because
- # the offset field is limited to 2^16-1 values. Thus, any offsets
+ # NOP is used to simulate long offset values, this is because
+ # the offset field is limited to 2^16-1 values. Thus, any offsets
# above 2^16 will be divided into a NOP + original relocation type.
offset += relocation.offset
continue
@@ -145,12 +190,13 @@ def rel_info(debug, rel_path, dump_header, dump_sections, dump_data, dump_reloca
if dump_data:
CONSOLE.print(f"Sections:")
-
- for i,section in enumerate(rel.sections):
+
+ for i, section in enumerate(rel.sections):
if section.data:
CONSOLE.print(f"\t#{i:<2}")
- hexdata = hexdump.hexdump(section.data, result='return')
+ hexdata = hexdump.hexdump(section.data, result="return")
CONSOLE.print(hexdata)
+
if __name__ == "__main__":
rel()
diff --git a/tools/requirements.txt b/tools/requirements.txt
index e4b338233a..8becbe26de 100644
--- a/tools/requirements.txt
+++ b/tools/requirements.txt
@@ -1,8 +1,10 @@
rich
click
-intervaltree
yaz0
-numpy
-capstone
-aiofiles
-GitPython \ No newline at end of file
+GitPython
+hexdump
+colorama
+ansiwrap
+watchdog
+python-Levenshtein
+cxxfilt \ No newline at end of file
diff --git a/tools/section2cpp.py b/tools/section2cpp.py
deleted file mode 100644
index 1187abc8fa..0000000000
--- a/tools/section2cpp.py
+++ /dev/null
@@ -1,612 +0,0 @@
-#!/usr/bin/env python3
-# PYTHON_ARGCOMPLETE_OK
-
-"""
-
-This script will extract literals and strings data
-from sections located in the baserom.dol.
-Useful when trying to match .rodata and .sdata2.
-
-Usage:
-./tools/section2cpp.py --section .rodata --string --object JKRSolidHeap.o
-
-"""
-
-import argparse
-import sys
-import os
-import struct
-from decimal import getcontext, Decimal
-from pathlib import Path, PurePath, PureWindowsPath
-from typing import (
- Any,
- Dict,
- List,
- Match,
- NamedTuple,
- NoReturn,
- Optional,
- Set,
- Tuple,
- Union,
- Callable,
- Pattern,
-)
-
-try:
- import numpy
-except:
- print("error: missing numpy")
- sys.exit(1)
-
-
-try:
- import argcomplete # type: ignore
-except ModuleNotFoundError:
- argcomplete = None
-
-parser = argparse.ArgumentParser(description="Extract section data and generate C++ code.")
-
-parser.add_argument(
- "--section",
- dest="section",
- type=str,
- metavar="SECTION",
- help="SECTION to extract data from.",
- required=True
-)
-
-parser.add_argument(
- "--file-offset",
- dest="file_offset",
- type=lambda x: int(x,0),
- metavar="OFFSET",
- help="OFFSET in the baserom for the SECTION."
-)
-
-parser.add_argument(
- "--object",
- dest="object_name",
- type=str,
- metavar="OBJECT",
- help="OBJECT filename to extract data from. (e.g. JKRSolidHeap.o)"
-)
-
-parser.add_argument(
- "--map",
- dest="map_path",
- type=str,
- metavar="MAP",
- help="frameworkF.map path",
- default="frameworkF.map"
-)
-
-parser.add_argument(
- "--baserom",
- dest="baserom",
- type=str,
- metavar="DOL",
- help="baserom.dol path",
- default="baserom.dol"
-)
-
-parser.add_argument(
- "--string",
- dest="as_string",
- action="store_true",
- help="Print arrays as strings"
-)
-
-parser.add_argument(
- "--array",
- dest="as_array",
- action="store_true",
- help="Print everything as u8 arrays"
-)
-
-parser.add_argument(
- "--shift-jis",
- dest="shift_jis",
- action="store_true",
- help="Convert shift-jis to utf-8"
-)
-
-
-#
-#
-#
-
-def _itersplit(l, splitters):
- current = []
- for item in l:
- if item in splitters:
- yield current
- current = []
- else:
- current.append(item)
- yield current
-
-def magicsplit(l, *splitters):
- return [subl for subl in _itersplit(l, splitters) ]
-
-def str_encoding(data):
- if data[-1] != 0:
- return None
-
- try:
- data.decode("utf-8")
- return "utf-8"
- except:
- pass
-
- try:
- data.decode("shift_jisx0213")
- return "shift-jis"
- except:
- pass
-
- return None
-
-def encoding_char_list(encoding, data):
- if args.shift_jis and encoding == "shift-jis":
- try:
- return escape(data.decode("shift_jisx0213"))
- except:
- pass
- return [ str(bytes([x]))[2:-1].replace("\"", "\\\"") for x in data ]
-
-def raw_string(data):
- return "".join(data)
-
-def raw_array(data):
- return ",".join([hex(x) for x in list(data)])
-
-def escape_char(v):
- if v == "\n":
- return "\\n"
- elif v == "\t":
- return "\\t"
- elif v == "\v":
- return "\\v"
- elif v == "\b":
- return "\\b"
- elif v == "\r":
- return "\\r"
- elif v == "\f":
- return "\\f"
- elif v == "\a":
- return "\\a"
- elif v == "\\":
- return "\\\\"
- elif v == "\"":
- return "\\\""
- elif ord(v) < 32 and ord(v) > 127:
- return "\\x" + hex(v)[2:].upper().rjust(2, '0')
- else:
- return v
-
-def escape(v):
- return "".join([ escape_char(x) for x in list(v) ])
-
-def bytes2float32(data):
- if len(data) < 4:
- return None
- result = numpy.frombuffer(data[0:4][::-1], dtype='float32')
- if result:
- return result[0]
- else:
- return None
-
-def bytes2float64(data):
- if len(data) < 8:
- return None
- result = numpy.frombuffer(data[0:8][::-1], dtype='float64')
- if result:
- return result[0]
- else:
- return None
-
-def is_nice_float32(f):
- try:
- if int(f*1000) == f*1000:
- return True
- if int(f*100) == f*100:
- return True
- if int(f*10) == f*10:
- return True
- if int(f) == f:
- return True
- except:
- return False
- return False
-
-def is_nice_float64(f):
- try:
- if int(f*1000) == f*1000:
- return True
- if int(f*100) == f*100:
- return True
- if int(f*10) == f*10:
- return True
- if int(f) == f:
- return True
- except:
- return False
- return False
-
-float32_exact: Dict[numpy.float32, Tuple[int,int]] = {}
-float64_exact: Dict[numpy.float64, Tuple[int,int]] = {}
-
-getcontext().prec = 64
-for i in range(1,32):
- for j in range(1,32):
- if i%j == 0:
- continue
- d = Decimal(i)/Decimal(j)
- f = numpy.float32(d)
- if str(f) != str(d):
- if not f in float32_exact:
- float32_exact[f] = (i,j)
-
-for i in range(1,32):
- for j in range(1,32):
- if i%j == 0:
- continue
- d = Decimal(i)/Decimal(j)
- f = numpy.float64(d)
- if str(f) != str(d):
- if not f in float64_exact:
- float64_exact[f] = (i,j)
-
-class Symbol:
- def __init__(self, name, addr, size):
- self.name = name
- self.addr = addr
- self.size = size
- self.padding = 0
-
- def __str__(self):
- return " %s %s %s+%s %s" % (self.name.ljust(40, ' '), hex(self.addr), hex(self.addr + self.size), hex(self.padding), hex(self.size))
-
-class ObjectFile:
- def __init__(self, path):
- self.path = path
- self.symbols = []
- self.start = 0
- self.end = 0
- self.mk = False
-
- def addSymbol(self, name, str_addr, str_size):
- addr = int(str_addr, base=16)
- size = int(str_size, base=16)
-
- symbol = Symbol(name, addr, size)
- if not self.symbols:
- self.start = symbol.addr
- else:
- last_symbol = self.symbols[-1]
- last_addr = last_symbol.addr + last_symbol.size
- if last_addr != addr:
- last_symbol.padding += addr - last_addr
- self.symbols += [ symbol ]
-
- def setEnd(self, end):
- self.end = end
- last_symbol = self.symbols[-1]
- last_symbol.padding = self.end - (last_symbol.addr + last_symbol.size)
-
-def find_symbols():
- file = map_path.open('r')
- lines = file.readlines()
-
- in_section = False
- last_obj = None
- for line in lines:
- data = [ x.strip() for x in line.strip().split(" ") ]
- data = [ x for x in data if len(x) > 0 ]
-
- if len(data) == 3:
- in_section = False
- if data[0] == section:
- in_section = True
- continue
-
- if not in_section:
- continue
- if len(data) < 6 or len(data) > 7:
- continue
-
- # get object filename
- obj = data[5]
- if len(data) > 6:
- obj = data[6]
-
- # remove path from object filename
- obj = obj.split("\\")[-1]
- if last_obj != obj:
- assert obj not in object_map
- object_map[obj] = ObjectFile(obj)
- last_obj = obj
-
- # add symbol
- size = data[1]
- addr = data[2]
- name = data[4]
- object_map[obj].addSymbol(name, addr, size)
-
- keys = list(object_map.keys())
- for i,_ in enumerate(keys[:-1]):
- obj = object_map[keys[i]]
- next_obj = object_map[keys[i + 1]]
- obj.setEnd(next_obj.start)
-
- # total size of rodata must be aligned to 0x20
- obj = object_map[keys[-1]]
- last_symbol = obj.symbols[-1]
- last_addr = last_symbol.addr + last_symbol.size
- last_symbol.padding = ((last_addr + 31) & ~31) - last_addr
- file.close()
-
-def chunks(lst, n):
- for i in range(0, len(lst), n):
- yield lst[i:i + n]
-
-def data_as_string(data):
- return ", ".join([ "0x" + hex(x)[2:].rjust(2, '0') for x in data ])
-
-
-class Literal:
- def __init__(self, name, type, value, comment=None):
- self.name = name
- self.type = type
- self.value = value
- self.comment = comment
-
- def format(self):
- return str(self.value)
-
- def lines(self):
- line = "static const %s %s = %s;" % (self.type, self.name, self.format())
- if self.comment:
- line = line.ljust(90, ' ') + " // " + self.comment
- return [ line ]
-
- def __str__(self):
- return "\n".join(self.lines())
-
-class Label(Literal):
- def __init__(self, name):
- super().__init__(name, "", None, None)
-
- def lines(self):
- return [ "", "", "// " + self.name ]
-
-class Float32Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "float", value, comment)
-
- def format(self):
- return "%sf" % self.value
-
-class Float64Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "double", value, comment)
-
-class FractionFloat32Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "float", value, comment)
-
- def format(self):
- return "%i.0f / %i.0f" % self.value
-
-class FractionFloat64Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "double", value, comment)
-
- def format(self):
- return "%i.0 / %i.0" % self.value
-
-class U32Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "u32", value, comment)
-
-class S32Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "s32", value, comment)
-
-class S64Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "s64", value, comment)
-
-class U64Literal(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "u64", value, comment)
-
-class ArrayLiteral(Literal):
- def __init__(self, name, value, comment=None):
- super().__init__(name, "u8", value, comment)
-
- def lines(self):
- one_line = "static const %s %s[%i] = { %s };" % (self.type, self.name, len(self.value), data_as_string(self.value))
-
- lines = []
- if len(one_line) < 90:
- lines += [ one_line ]
- else:
- lines += [ "static const %s %s[%i] = {" % (self.type, self.name, len(self.value)) ]
- data_chunks = chunks(list(self.value), 16)
- for chunk in data_chunks:
- lines += [ " " + data_as_string(chunk) ]
- lines += [ "};" ]
-
- if lines and self.comment:
- lines[0] = lines[0].ljust(90, ' ') + " // " + self.comment
- return lines
-
-class StringLiteral(Literal):
- def __init__(self, name, encoding, value, comment=None):
- assert value[-1] == 0
- super().__init__(name, "char", value[:-1], comment)
- self.encoding = encoding
-
- def lines(self):
- char_list = encoding_char_list(self.encoding, self.value)
- one_line = "static const %s %s = \"%s\";" % (self.type, self.name, raw_string(char_list))
-
- lines = []
- if len(one_line) < 90:
- lines += [ one_line ]
- else:
- lines += [ "static const %s %s = " % (self.type, self.name) ]
- data_chunks = chunks(char_list, 16)
- for chunk in data_chunks:
- lines += [ " \"%s\"" % raw_string(chunk) ]
- lines[-1] += ";"
-
- if lines and self.comment:
- lines[0] = lines[0].ljust(90, ' ') + " // " + self.comment
- return lines
-
-def output_cpp():
- object_names = []
- if object_name:
- if not object_name in object_map:
- print("error: %s object file not found!" % object_name)
- sys.exit(1)
- object_names += [ object_name ]
- else:
- object_names = [*object_map.keys()]
-
- br = baserom.open("rb")
- br.seek(0, os.SEEK_END)
- br_size = br.tell()
- br.seek(0, os.SEEK_SET)
-
- literals = []
- for obj_name in object_names:
- literals += [ Label(obj_name) ]
-
- obj = object_map[obj_name]
- for symbol in obj.symbols:
- label = "lbl_%s" % (hex(symbol.addr).upper()[2:])
-
- symbol_file_offset = symbol.addr - file_offset
- symbol_file_size = symbol.size + symbol.padding
-
- if symbol_file_offset + symbol_file_size > br_size:
- print("error: reading outside baserom file. (%i, %i)" % (symbol_file_offset + symbol_file_size, br_size))
-
- br.seek(symbol_file_offset, os.SEEK_SET)
- data = br.read(symbol.size)
- padding = br.read(symbol.padding)
-
- if args.as_string:
- offset = 0
- str_segments = [ x for x in magicsplit(data, 0) ]
- for segment in str_segments[:-1]:
- str_data = bytes(segment + [0])
- encoding = str_encoding(str_data)
-
- str_label = "lbl_%s" % (hex(symbol.addr + offset).upper()[2:])
- if encoding == "shift-jis":
- literals += [ StringLiteral(str_label, "shift-jis", str_data, "TODO: shift-jis strings in Metrowerks") ]
- elif encoding == "utf-8":
- literals += [ StringLiteral(str_label, "utf-8", str_data) ]
- else:
- literals += [ ArrayLiteral(str_label, str_data, "undecodable string") ]
- offset += len(str_data)
-
- if padding:
- padding_label = "lbl_%s" % (hex(symbol.addr + symbol.size).upper()[2:])
- literals += [ StringLiteral(padding_label, None, padding, "padding") ]
- padding = None
- elif args.as_array:
- literals += [ ArrayLiteral(label, data) ]
- else:
- lit = None
- if len(data) == 4:
- u32_data = struct.unpack('>I', data)[0]
- s32_data = struct.unpack('>i', data)[0]
- float_data = bytes2float32(data)
-
- if s32_data == 0 or (s32_data >= -4096 and s32_data <= 4096):
- lit = S32Literal(label, s32_data)
- elif u32_data == 0 or (u32_data < 4096):
- lit = U32Literal(label, u32_data)
- elif float_data in float32_exact:
- lit = FractionFloat32Literal(label, float32_exact[float_data], "%sf %s" % (float_data, hex(u32_data)))
- elif is_nice_float32(float_data):
- lit = Float32Literal(label, float_data, hex(u32_data))
-
- elif len(data) == 8:
- u64_data = struct.unpack('>Q', data)[0]
- s64_data = struct.unpack('>q', data)[0]
- double_data = bytes2float64(data)
-
- if u64_data == 0x4330000000000000:
- lit = Float64Literal(label, double_data, "%s | u32 to float (compiler-generated)" % hex(u64_data))
- elif u64_data == 0x4330000080000000:
- lit = Float64Literal(label, double_data, "%s | s32 to float (compiler-generated)" % hex(u64_data))
- elif s64_data == 0 or (s64_data >= -4096 and s64_data <= 4096):
- lit = S64Literal(label, s64_data)
- elif u64_data == 0 or (u64_data < 4096):
- lit = U64Literal(label, u64_data)
- elif double_data in float64_exact:
- lit = FractionFloat64Literal(label, float64_exact[double_data], "%s %s" % (double_data, hex(u64_data)))
- elif is_nice_float64(double_data):
- lit = Float64Literal(label, double_data, hex(u64_data))
-
- if not lit:
- lit = ArrayLiteral(label, data)
- literals += [ lit ]
-
- if padding:
- padding_label = "lbl_%s" % (hex(symbol.addr + symbol.size).upper()[2:])
- literals += [ ArrayLiteral(padding_label, padding, "padding") ]
-
- for lit in literals:
- print(lit)
-
- br.close()
-
-#
-#
-#
-
-try:
- args = parser.parse_args()
-except:
- parser.print_help()
- sys.exit(0)
-
-section = args.section
-object_name = args.object_name
-file_offset: Optional[int] = args.file_offset
-baserom = Path(args.baserom)
-map_path = Path(args.map_path)
-
-file_offsets = {
- ".rodata": 0x80003000,
- ".sdata": 0x800802A0,
- ".sdata2": 0x800811A0,
-}
-
-if not file_offset:
- if not section in file_offsets:
- print("error: missing --file-offset")
- sys.exit(1)
- else:
- file_offset = file_offsets[section]
-
-if not baserom.exists():
- print("error: baserom '%s' not found!" % args.baserom)
- sys.exit(1)
-
-if not map_path.exists():
- print("error: frameworkF.map '%s' not found!" % args.map_path)
- sys.exit(1)
-
-object_map: Dict[str,ObjectFile] = {}
-
-find_symbols()
-output_cpp()
diff --git a/tools/splitter/asm_parser.py b/tools/splitter/asm_parser.py
deleted file mode 100644
index d5c723f243..0000000000
--- a/tools/splitter/asm_parser.py
+++ /dev/null
@@ -1,159 +0,0 @@
-from dataclasses import dataclass
-from parsy import string, regex, seq, generate, line_info
-from typing import Optional, List, Union, Protocol
-
-
-class Emittable(Protocol):
- def emit(self) -> str:
- ...
-
-
-@dataclass
-class BlockComment:
- text: str
-
- def emit(self) -> str:
- return f'/*{self.text}*/'
-
-
-@dataclass
-class TrailingComment:
- text: str
-
- def emit(self) -> str:
- return f'# {self.text}'
-
-
-@dataclass
-class Include:
- file: str
-
- def emit(self) -> str:
- return f'.include "{self.file}"'
-
-
-@dataclass
-class Section:
- name: str
- flags: Optional[str]
-
- def emit(self) -> str:
- directive = f'.section .{self.name}'
- if self.flags is not None:
- directive += f', "{self.flags}"'
-
- return directive
-
-
-@dataclass
-class Global:
- symbol: str
-
- def emit(self) -> str:
- return f'.global {self.symbol}'
-
-
-@dataclass
-class Label:
- symbol: str
-
- def emit(self) -> str:
- return f'{self.symbol}:'
-
-
-@dataclass
-class Instruction:
- opcode: str
- operands: List[str]
-
- def emit(self) -> str:
- instr = self.opcode
- if len(self.operands) > 0:
- instr += ' ' + ', '.join(self.operands)
-
- return instr
-
-
-@dataclass
-class Line:
- index: int
- content: List[
- Union[
- BlockComment, TrailingComment, Instruction, Global, Section, Include, Label
- ]
- ]
- body: Optional[Union[Global, Section, Include, Label, Instruction]]
-
- def emit(self) -> str:
- return ' '.join([x.emit() for x in self.content])
-
-
-space = regex(r'[ \t]+')
-line_ending = regex('(\n)|(\r\n)').desc('newline')
-pad = regex(r'[ \t]*')
-
-
-block_comment = (
- string('/*') >> regex(r'[\w\s]*').map(BlockComment) << string('*/')
-).desc('block comment')
-trailing_comment = (
- string('#') >> pad >> regex(r'[^\n\r]*').map(TrailingComment)
-).desc('trailing comment')
-
-symbolname = regex(r'[a-zA-Z._$][a-zA-Z0-9._$?]*')
-label = (symbolname.map(Label) << string(':')).desc('label')
-
-delimited_string = (string('"') >> regex(r'[^"]*') << string('"')).desc(
- 'double-quote delimited string'
-)
-
-directive_include = string('include') >> space >> delimited_string.map(Include)
-directive_section = seq(
- name=string('section')
- >> space
- >> string('.')
- >> regex(r'[a-z]+'),
- flags=(pad >> string(',') >> space >> delimited_string).optional(),
-).combine_dict(Section)
-directive_global = string('global') >> space >> symbolname.map(Global)
-directive = (
- string('.')
- >> (
- directive_include
- | directive_section
- | directive_global
- | string('text').result(Section('text', flags=None))
- | string('data').result(Section('data', flags=None))
- )
-).desc('directive')
-
-opcode = regex(r'[a-z_0-9]+\.?').concat().desc('opcode')
-operand = regex(r'[^,#\s]+')
-operands = operand.sep_by(string(',') << pad)
-@generate
-def instruction():
- op = yield opcode
- sp = yield space.optional()
- if sp:
- oprs = yield operands
- else:
- oprs = []
- return Instruction(op, oprs)
-
-@generate
-def line():
- line, _ = yield line_info
-
- content = yield (pad >> block_comment << pad).many()
- body = yield (directive | label | instruction).optional() << pad
- if body:
- content.append(body)
- content += yield (pad >> block_comment).many()
- trailing = yield (pad >> trailing_comment).optional()
- if trailing:
- content.append(trailing)
-
- return Line(line, content, body)
-
-
-asm = line.sep_by(line_ending)
diff --git a/tools/splitter/demangle.py b/tools/splitter/demangle.py
deleted file mode 100644
index 5eb89c8add..0000000000
--- a/tools/splitter/demangle.py
+++ /dev/null
@@ -1,311 +0,0 @@
-from typing import List, Optional
-from pathlib import Path
-from dataclasses import dataclass, field
-import re
-
-operator_func_re = re.compile(r'^__([a-z]+)')
-
-types = {
- 'i': 'int',
- 'l': 'long',
- 's': 'short',
- 'c': 'char',
- 'f': 'f32',
- 'd': 'f64',
- 'v': 'void',
- 'x': 'long long',
- 'b': 'bool',
- 'e': 'varargs...',
-}
-
-short_type_names = {
- 'char': '8',
- 'short': '16',
- 'long' : '32',
- 'long long': '64',
-}
-
-# {'defctor', 'ops',}
-
-special_funcs = {
- 'eq': 'operator==',
- 'as': 'operator=',
- 'ne': 'operator!=',
- 'dv': 'operator/',
- 'pl': 'operator+',
- 'mi': 'operator-',
- 'ml': 'operator*',
- 'adv': 'operator/=',
- 'apl': 'operator+=',
- 'ami': 'operator-=',
- 'amu': 'operator*=',
- 'lt': 'operator<',
- 'gt': 'operator>',
- 'cl': 'operator()',
- 'dla': 'operator delete[]',
- 'nwa': 'operator new[]',
- 'dl': 'operator delete',
- 'nw': 'operator new',
-}
-
-
-@dataclass
-class Param:
- name: str = ''
- pointer_lvl: int = 0
- is_const: bool = False
- is_ref: bool = False
- is_unsigned: bool = False
- is_signed: bool = False
-
- def to_str(self) -> str:
- ret = ''
- if self.is_const:
- ret += 'const '
- if self.name in short_type_names:
- ret += 'u' if self.is_unsigned else 's'
- ret += short_type_names[self.name]
- else:
- if self.is_unsigned:
- ret += 'unsigned '
- ret += self.name
- for _ in range(self.pointer_lvl):
- ret += '*'
- if self.is_ref:
- ret += '&'
- return ret
-
-
-@dataclass
-class FuncParam:
- ret_type: Optional[str] = None
- params: List[str] = field(default_factory=list)
-
- def to_str(self) -> str:
- ret = ''
- if self.ret_type is None:
- ret += 'void'
- else:
- ret += self.ret_type
- ret += ' (*)('
- ret += ', '.join(self.params)
- ret += ')'
- return ret
-
-
-class ParseError(Exception):
- ...
-
-
-class ParseCtx:
- def __init__(self, mangled: str):
- self.mangled = mangled
- self.index = 0
- self.demangled = []
- self.cur_type = None
- self.class_name = None
- self.is_const = False
- self.func_name = None
-
- def demangle(self):
- # this split is still not accurate, but good enough for most cases
- last_f = self.mangled.rfind('F')
- if last_f == -1:
- return
- split_pos = self.mangled.rfind('__', 0, last_f)
- if split_pos == -1 or split_pos == 0:
- return
- self.func_name = self.mangled[:split_pos]
- self.mangled = self.mangled[split_pos+2:]
- if self.func_name.startswith('__'):
- match = operator_func_re.match(self.func_name)
- if match:
- special_func_name = match.group(1)
- if special_func_name in special_funcs:
- self.func_name = special_funcs[special_func_name]
- else:
- if special_func_name == 'ct':
- self.func_name = '.ctor'
- elif special_func_name == 'dt':
- self.func_name = '.dtor'
- self.demangle_first_class()
- while self.index < len(self.mangled):
- self.demangled.append(self.demangle_next_type())
- if self.func_name == '.ctor':
- self.func_name = self.class_name
- if self.func_name == '.dtor':
- self.func_name = '~' + self.class_name
-
- def demangle_first_class(self):
- if self.peek_next_char().isdecimal():
- self.class_name = self.demangle_class()
- if self.peek_next_char() == 'C':
- self.is_const = True
- self.index += 1
- assert self.consume_next_char() == 'F', 'next char should be F!'
- elif self.peek_next_char() == 'Q':
- self.index += 1
- self.class_name = self.demangle_qualified_name()
- if self.peek_next_char() == 'C':
- self.is_const = True
- self.index += 1
- assert self.consume_next_char() == 'F', 'next char should be F!'
- else:
- assert self.consume_next_char() == 'F', 'next char should be F!'
-
- def demangle_next_type(self) -> str:
- cur_type = Param()
- while True:
- cur_char = self.peek_next_char()
- if cur_char.isdecimal():
- class_name = self.demangle_class()
- cur_type.name = class_name
- return cur_type.to_str()
- elif cur_char in types:
- type_name = self.demangle_prim_type()
- cur_type.name = type_name
- return cur_type.to_str()
- elif cur_char == 'U':
- cur_type.is_unsigned = True
- self.index += 1
- elif cur_char == 'S':
- cur_type.is_signed = True
- self.index += 1
- elif cur_char == 'C':
- cur_type.is_const = True
- self.index += 1
- elif cur_char == 'P':
- cur_type.pointer_lvl += 1
- self.index += 1
- elif cur_char == 'R':
- cur_type.is_ref = True
- self.index += 1
- elif cur_char == 'F':
- self.index += 1
- func = self.demangle_function()
- return func.to_str()
- elif cur_char == 'Q':
- self.index += 1
- return self.demangle_qualified_name()
- elif cur_char == 'A':
- if cur_type.pointer_lvl < 1 and not cur_type.is_ref:
- raise ParseError("pointer level for array is wrong!")
- # decrease pointer level by one, cause one is already handled in the array demangle
- if not cur_type.is_ref:
- cur_type.pointer_lvl -= 1
- cur_type.name = self.demangle_array()
- return cur_type.to_str()
-
- else:
- raise ParseError(f'unexpected character {cur_char}')
-
- def demangle_array(self) -> str:
- sizes = []
- while self.peek_next_char() == 'A':
- self.index += 1
- sizes.append(self.read_next_int())
- if self.consume_next_char() != '_':
- raise ParseError("Need to have '_' after Array size!")
- array_type = self.demangle_next_type()
- return f'{array_type} []' + ''.join(f'[{i}]' for i in sizes)
-
- def demangle_function(self) -> FuncParam:
- func_param = FuncParam()
- while True:
- cur_char = self.peek_next_char()
- if cur_char == '_':
- self.index += 1
- func_param.ret_type = self.demangle_next_type()
- return func_param
- func_param.params.append(self.demangle_next_type())
-
- def demangle_qualified_name(self) -> str:
- part_count = int(self.consume_next_char())
- parts = []
- for _ in range(part_count):
- parts.append(self.demangle_class())
- return '::'.join(parts)
-
- def read_next_int(self) -> int:
- class_len_str = ''
- cur_char = self.peek_next_char()
- while cur_char.isdecimal():
- class_len_str += cur_char
- self.index += 1
- cur_char = self.peek_next_char()
- return int(class_len_str)
-
- def demangle_class(self) -> str:
- if not self.peek_next_char().isdecimal():
- raise ParseError(f'class mangling must start with number')
- class_len = self.read_next_int()
- class_name = self.mangled[self.index : self.index + class_len]
- self.index += class_len
- if self.peek_next_char() == 'M':
- self.index += 1
- class_name += '::' + self.demangle_class()
- return class_name
-
- def demangle_prim_type(self) -> str:
- ret = types[self.consume_next_char()]
- return ret
-
- def consume_next_char(self) -> str:
- next_char = self.mangled[self.index]
- self.index += 1
- return next_char
-
- def peek_next_char(self) -> str:
- if self.index >= len(self.mangled):
- return None
- return self.mangled[self.index]
-
- def to_str(self) -> str:
- if self.func_name is None:
- return ''
- elif self.class_name is None:
- return self.func_name + '(' + ', '.join(self.demangled) + ')'
- else:
- return self.class_name + '::' + self.func_name + '(' + ', '.join(self.demangled) + ')' + (' const' if self.is_const else '')
-
-
-def demangle(s):
- p = ParseCtx(s)
- p.demangle()
- return p.to_str()
-
-
-def parse_framework_map(path: Path):
- address_funcname = {}
- with path.open() as f:
- for line in f.readlines():
- if line.startswith('.ctors'):
- return address_funcname
- if not line.startswith(' '):
- continue
- funcname = line[30:].split(' ', 1)[0]
- address = line[18:26]
- address_funcname[address] = funcname
- return address_funcname
-
-# def try_demangle_all():
-# with open('frameworkF.map') as f:
-# for line in f.readlines():
-# if line.startswith('.ctors'):
-# return
-# if not line.startswith(' '):
-# continue
-# line = line[30:]
-# line_spl = line.split(' ',1)[0]
-# try:
-# d = demangle(line_spl)
-# if d:
-# print(d)
-# # except NotImplementedError:
-# # pass
-# except Exception as e:
-# # print(f'could not demangle {line_spl}: {repr(e)}')
-# # raise e
-# pass
-
-# try_demangle_all() \ No newline at end of file
diff --git a/tools/splitter/split.py b/tools/splitter/split.py
deleted file mode 100644
index 1a06578e6d..0000000000
--- a/tools/splitter/split.py
+++ /dev/null
@@ -1,338 +0,0 @@
-"""
-split.py - 202x erin moon for zeldaret
-"""
-
-from typing import Iterable, List
-from dataclasses import dataclass
-from pathlib import Path, PosixPath
-from textwrap import dedent
-from loguru import logger
-from datetime import datetime
-import re
-import click
-from asm_parser import asm, Emittable, Global, Label, Line, BlockComment, Instruction
-from demangle import parse_framework_map, demangle
-from util import PathPath, pairwise
-from pprint import pprint
-import pickle
-import IPython
-
-SDA_BASE = 0x80458580
-SDA2_BASE = 0x80459A00
-
-__version__ = 'v0.4'
-
-
-def function_global_search(lines: List[Line]) -> Iterable[Line]:
- i = 0
- while i < len(lines):
- if isinstance(lines[i].body, Global):
- sym = lines[i].body.symbol
- if isinstance(lines[i + 1].body, Label) and lines[i + 1].body.symbol == sym:
- yield lines[i]
- i += 2
- else:
- i += 1
-
-
-def emit_lines(lines: List[Line]) -> str:
- return '\n'.join([line.emit() for line in lines])
-
-
-def comment_out(line: Line) -> Line:
- return Line(line.index, [BlockComment(line.emit())], None)
-
-
-def fix_sda_base_add(line: Line) -> Line:
- if 'SDA' in line.body.operands[2]:
- ops = line.body.operands[2].split('-')
- lbl_name = ops[0]
- if ops[1] == '_SDA_BASE_':
- sda_reg = 'r13'
- elif ops[1] == '_SDA2_BASE_':
- sda_reg = 'r2'
- else:
- logger.error('Unknown SDABASE!')
- return line
-
- line.body.opcode = 'la'
- line.body.operands = [line.body.operands[0], f'{lbl_name}({sda_reg})']
- return line
-
-QUANT_REG_RE = re.compile(r'qr(\d+)')
-def patch_gqrs(line: Line) -> Line:
- line.body.operands = [QUANT_REG_RE.sub(r'\1', o) for o in line.body.operands]
-
- return line
-
-@dataclass
-class Function:
- name: str
- addr: int
- lines: List[Line]
-
- @property
- def line_count(self):
- return len(self.lines)
-
- @property
- def filename(self) -> str:
- return f'func_{self.addr:X}.s'
-
- def include_path(self, base: Path) -> str:
- return str(PosixPath(base) / PosixPath(self.filename))
-
-
-def find_functions(lines: List[Line], framework_map) -> Iterable[Function]:
- for func_global_line, next_func_global_line in pairwise(
- function_global_search(lines)
- ):
- # some blocks weren't properly split, use the map to find missing functions
- fr = func_global_line.index + 2
- # if no next global, to = None => end of file
- to = next_func_global_line and next_func_global_line.index
- func_lines = lines[fr:to]
- func_idx = []
- for idx, line in enumerate(func_lines):
- if isinstance(line.body, Instruction):
- addr = int(line.content[0].text.strip().split()[0], 16)
- if f'{addr:x}' in framework_map:
- func_idx.append(idx)
-
- for start_idx, end_idx in pairwise(func_idx):
- sub_func_lines = func_lines[
- start_idx : (len(func_lines) if end_idx == None else end_idx)
- ]
-
- addr = int(sub_func_lines[0].content[0].text.strip().split()[0], 16)
-
- yield Function(
- name=func_global_line.body.symbol
- if start_idx == 0
- else f'func_{addr:X}',
- addr=addr,
- lines=sub_func_lines,
- )
-
-
-def emit_cxx_asmfn(inc_base: Path, func: Function) -> str:
- return dedent(
- '''\
- asm void {name}(void) {{
- nofralloc
- #include "{inc}"
- }}'''.format(
- name=func.name, inc=func.include_path(inc_base)
- )
- )
-
-
-def emit_cxx_extern_fns(tu_file: str, labels: Iterable[str]) -> str:
- def decl(label):
- return f'void {label}(void);'
-
- defs = '\n '.join(decl(label) for label in sorted(labels))
-
- return (
- f'// additional symbols needed for {tu_file}\n'
- f'// autogenerated by split.py {__version__} at {datetime.utcnow()}\n'
- 'extern "C" {\n'
- ' ' + defs + '\n}'
- )
-
-
-def emit_cxx_extern_vars(tu_file: str, labels: Iterable[str]) -> str:
- def decl(label):
- return f'extern u8 {label};'
-
- return (
- f'// additional symbols needed for {tu_file}\n'
- f'// autogenerated by split.py {__version__} at {datetime.utcnow()}\n'
- + '\n'.join(decl(label) for label in sorted(labels))
- + '\n'
- )
-
-
-@click.command()
-@click.argument('src', type=PathPath(file_okay=True, dir_okay=False, exists=True))
-@click.argument('cxx_out', type=PathPath(file_okay=True, dir_okay=False))
-@click.option(
- '--funcs-out',
- type=PathPath(file_okay=False, dir_okay=True),
- default='include/funcs',
-)
-@click.option('--s-include-base', type=str, default='funcs')
-@click.option(
- '--framework-map-file',
- type=PathPath(file_okay=True, dir_okay=False),
- default='frameworkF.map',
-)
-@click.option(
- '--ldscript-file',
- type=PathPath(file_okay=True, dir_okay=False),
- default='ldscript.lcf',
-)
-@click.option('--from-line', type=int)
-@click.option('--to-line', type=int)
-@click.option('--preparsed', is_flag=True)
-@click.option('--forceactive',
- type=click.Choice(['all', 'none', 'missingfunc']), default='missingfunc')
-def split(
- src,
- cxx_out,
- funcs_out,
- s_include_base,
- framework_map_file,
- ldscript_file,
- from_line,
- to_line,
- preparsed,
- forceactive
-):
- funcs_out.mkdir(exist_ok=True, parents=True)
-
- if preparsed:
- logger.info('loading preparsed assembly')
- with src.open('rb') as f:
- lines = pickle.load(f)
- else:
- logger.info('parsing assembly')
- lines = asm.parse(src.read_text())
- lines = lines[
- (from_line - 1 if from_line else 0) : (to_line - 1 if to_line else -1)
- ]
-
- logger.info('parsing map file')
- framework_map = parse_framework_map(framework_map_file)
- logger.debug(f'loaded {len(framework_map)} symbols from map')
-
- logger.info('reading ldscript')
- ldscript_file_content = ldscript_file.read_text()
- new_ldfuncs = []
-
- # -- get all defined labels and jump targets
- jumped_labels = set()
- defined_labels = set()
-
- logger.info('scanning for branch targets')
- for line in lines:
- if isinstance(line.body, Label):
- defined_labels.add(line.body.symbol)
- if isinstance(line.body, Instruction):
- if line.body.opcode[0] == 'b' and line.body.operands != []: # branch
- jumped_labels.add(line.body.operands[0]) # jump target
-
- # -- find everything of the form lbl_[hex] that's in an operand on the RHS of a l* instruction
- # this is a relatively okay assumption given that any var that's *not* of the form lbl_ has
- # probably already been renamed and thus is exported in variables.h
- LBL_RE = re.compile(r'lbl_[0-9A-F]+')
-
- def find_labels_in_operands(operands):
- for operand in operands:
- if match := LBL_RE.search(operand):
- yield match.group()
-
- logger.info('scanning for load/store target labels')
- loaded_labels = set()
- for line in lines:
- if isinstance(line.body, Instruction):
- if line.body.opcode[0] in {'l', 's'} or line.body.opcode == 'addi': # load and store instructions, ish. Also addi for loading SDA addresses
- loaded_labels |= set(find_labels_in_operands(line.body.operands))
-
- # -- find all defined functions and split them
- functions = list(find_functions(lines, framework_map))
- logger.info('splitting functions')
- for func in functions:
-
- logger.debug(
- f'working on function {func.name} @ {func.addr:X} with {func.line_count} lines'
- )
-
- # comment out .globals
- func.lines = [
- comment_out(line) if isinstance(line.body, Global) else line
- for line in func.lines
- ]
-
- # fix SDA_BASE addi
- func.lines = [
- fix_sda_base_add(line)
- if isinstance(line.body, Instruction)
- and line.body.opcode == 'addi'
- else line
- for line in func.lines
- ]
-
- # remove GQR mnemonics
- func.lines = [
- patch_gqrs(line)
- if isinstance(line.body, Instruction)
- and line.body.opcode.startswith('psq_')
- else line
- for line in func.lines
- ]
-
- # check if needs to be defined in ldscript
- if forceactive != 'none':
- if not func.name in ldscript_file_content and (forceactive=='all' or func.name.startswith('func_')):
- new_ldfuncs.append(func.name)
-
- with open(out_path := funcs_out / func.filename, 'w') as f:
- logger.debug(f'emitting {out_path}')
- f.write(emit_lines(func.lines))
-
- # -- dump new labels to functions.h
- logger.info('dumping labels to extern functions header')
- func_labels = jumped_labels - defined_labels
- # add in everything we def to make sure asm can get backrefs
- for func in functions:
- func_labels.add(func.name)
-
- # -- write asm stubs to cxx_out (could've done this as part of previous loop but imo this is cleaner)
- logger.info(f'emitting c++ asm stubs to {cxx_out}')
- with open(cxx_out, 'w') as f:
- f.write(
- f'/* {cxx_out.name} autogenerated by split.py {__version__} at {datetime.utcnow()} */\n\n'
- )
- f.write('#include "global.h"\n\n')
-
- # extern functions
- f.write(emit_cxx_extern_fns(cxx_out.name, func_labels))
- f.write('\n\n')
-
- # extern variables
- f.write(emit_cxx_extern_vars(cxx_out.name, loaded_labels))
- f.write('\n\n')
-
- f.write('extern "C" {\n')
- for func in functions:
- logger.debug(f'emitting asm stub for {func.name}')
- mangled_func_name = framework_map[f'{func.addr:x}']
- f.write(f'// {mangled_func_name}\n')
- try:
- demangled_func_name = demangle(mangled_func_name)
- f.write(f'// {demangled_func_name}\n')
- except Exception as e:
- logger.warning(f"could not demangle symbol '{mangled_func_name}': {e}")
-
- f.write(emit_cxx_asmfn(s_include_base, func))
- f.write('\n\n')
-
- f.write('};\n') # extern C end
-
- # -- make defined functions FORCEACTIVE in ldscript.lcf
- logger.info(f'writing to FORCEACTIVE in linker script')
- forceactive_start = ldscript_file_content.find('FORCEACTIVE')
- forceactive_end = ldscript_file_content.find('}', forceactive_start)
- for func in new_ldfuncs:
- ldscript_file_content = (
- ldscript_file_content[:forceactive_end]
- + func
- + '\n'
- + ldscript_file_content[forceactive_end:]
- )
- ldscript_file.write_text(ldscript_file_content)
-
-
-if __name__ == '__main__':
- split()
diff --git a/tools/splitter/util.py b/tools/splitter/util.py
deleted file mode 100644
index 260f6d6a4e..0000000000
--- a/tools/splitter/util.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import itertools
-import click
-from pathlib import Path
-
-
-def pairwise(iterable):
- "s -> (s0,s1), (s1,s2), (s2, s3), ..."
- a, b = itertools.tee(iterable)
- next(b, None)
- return itertools.zip_longest(a, b)
-
-
-class PathPath(click.Path):
- """A Click path argument that returns a pathlib Path, not a string"""
-
- def convert(self, value, param, ctx):
- return Path(super().convert(value, param, ctx))
diff --git a/tools/tp.py b/tools/tp.py
index 40f3ed9408..ce9d0bdab3 100644
--- a/tools/tp.py
+++ b/tools/tp.py
@@ -1,44 +1,53 @@
"""
-tp.py - Various tools used for the zeldaret/tp project
-
-progress: Calculates decompilation progress. By assuming that the code was generated by 'dol2asm'
-and that all non-code sections are fully decompiled. The script calculate the amount of bytes
-that are left to decompile (all code in the .s files).
-
-pull-request: Helps people make sure that everything is OK before making pull-requests.
-The script does three things: remove unused asm files, rebuild the full project, and
-clang-format every file.
+tp.py - Various tools used for the zeldaret/tp project.
"""
-import click
-import sys
import os
-import rich
-import logging
-import subprocess
+import sys
import time
-import hashlib
-import json
-import git
-import libdol
-import librel
-import libarc
-import yaz0
import struct
-import io
+import json
+import subprocess
+import multiprocessing as mp
+import shutil
-from pathlib import Path
-from rich.logging import RichHandler
-from rich.console import Console
-from rich.progress import Progress
-from rich.text import Text
-from rich.table import Table
from dataclasses import dataclass, field
from typing import Dict
+from pathlib import Path
+
+try:
+ import click
+ import logging
+ import hashlib
+ import git
+ import libdol
+ import librel
+ import libarc
+ import yaz0
+ import io
+ import extract_game_assets
+
+ from rich.logging import RichHandler
+ from rich.console import Console
+ from rich.progress import Progress
+ from rich.text import Text
+ from rich.table import Table
+except ImportError as e:
+ MISSING_PREREQUISITES = (
+ f"Missing prerequisite python module {e}.\n"
+ f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites."
+ )
+
+ print(MISSING_PREREQUISITES, file=sys.stderr)
+ sys.exit(1)
+
+
+class PathPath(click.Path):
+ def convert(self, value, param, ctx):
+ return Path(super().convert(value, param, ctx))
-import multiprocessing as mp
VERSION = "1.0"
CONSOLE = Console()
@@ -47,7 +56,7 @@ logging.basicConfig(
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
- handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)]
+ handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)],
)
LOG = logging.getLogger("rich")
@@ -57,22 +66,250 @@ loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
for logger in loggers:
logger.setLevel(logging.INFO)
-DEFAULT_GAME_PATH = Path("game")
-DEFAULT_BUILD_PATH = Path("build/dolzel2")
+DEFAULT_GAME_PATH = "game"
+DEFAULT_TOOLS_PATH = "tools"
+DEFAULT_BUILD_PATH = "build/dolzel2"
+DEFAULT_EXPECTED_PATH = "expected/build/dolzel2"
+
@click.group()
@click.version_option(VERSION)
def tp():
- """ Tools to help the decompilation of "The Legend of Zelda: Twilight Princess" """
+ """Tools to help the decompilation of "The Legend of Zelda: Twilight Princess" """
pass
+
+@tp.command(name="expected")
+@click.option("--debug/--no-debug")
+@click.option(
+ "--build-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_BUILD_PATH,
+ required=True,
+)
+@click.option(
+ "--expected-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_EXPECTED_PATH,
+ required=True,
+)
+def expected_copy(debug, build_path, expected_path):
+ """Copy the current build folder to the expected folder"""
+
+ if debug:
+ LOG.setLevel(logging.DEBUG)
+
+ if not build_path.exists() or not build_path.is_dir():
+ LOG.error(
+ f"You need to successfully build the project before copy into expected folder."
+ )
+ sys.exit(1)
+
+ shutil.rmtree(expected_path, ignore_errors=True)
+ expected_path.mkdir(exist_ok=True, parents=True)
+
+ CONSOLE.log(f"src: '{build_path}'")
+ CONSOLE.log(f"dst: '{expected_path}'")
+ shutil.copytree(build_path, expected_path, dirs_exist_ok=True)
+
+
+@tp.command(name="setup")
+@click.option("--debug/--no-debug")
+@click.option(
+ "--game-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_GAME_PATH,
+ required=True,
+)
+@click.option(
+ "--tools-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_TOOLS_PATH,
+ required=True,
+)
+def setup(debug, game_path, tools_path):
+ """Setup project"""
+
+ if debug:
+ LOG.setLevel(logging.DEBUG)
+
+ #
+ text = Text("--- Creating directories")
+ text.stylize("bold magenta")
+ CONSOLE.print(text)
+ if not game_path.exists():
+ game_path.mkdir(parents=True, exist_ok=True)
+
+ if not tools_path.exists() or not tools_path.is_dir():
+ LOG.error(f"Tools directory missing '{tools_path}'")
+ sys.exit(1)
+
+ #
+ text = Text("--- Patching compiler")
+ text.stylize("bold magenta")
+ CONSOLE.print(text)
+
+ compilers = tools_path.joinpath("mwcc_compiler")
+ if not compilers.exists() or not compilers.is_dir():
+ LOG.error(
+ (
+ f"Unable to find MWCC compilers: missing directory '{compilers}'\n"
+ f"Check the README for instructions on how to obtain the compilers"
+ )
+ )
+ sys.exit(1)
+
+ c27 = compilers.joinpath("2.7")
+ if not c27.exists() or not c27.is_dir():
+ LOG.error(
+ (
+ f"Unable to find MWCC compiler version 2.7: missing directory '{c27}'\n"
+ f"Check the README for instructions on how to obtain the compilers"
+ )
+ )
+ sys.exit(1)
+
+ c27_lmgr326b = c27.joinpath("Lmgr326b.dll")
+ if not c27_lmgr326b.exists() or not c27_lmgr326b.is_file():
+ c27_lmgr326b = c27.joinpath("lmgr326b.dll")
+ if not c27_lmgr326b.exists() or not c27_lmgr326b.is_file():
+ c27_lmgr326b = c27.joinpath("LMGR326B.dll")
+ if not c27_lmgr326b.exists() or not c27_lmgr326b.is_file():
+ c27_lmgr326b = c27.joinpath("LMGR326B.DLL")
+ if not c27_lmgr326b.exists() or not c27_lmgr326b.is_file():
+ LOG.error(
+ (
+ f"Unable to find 'lmgr326b.dll' in '{c27}': missing file '{c27_lmgr326b}'\n"
+ f"Check the README for instructions on how to obtain the compilers"
+ )
+ )
+ sys.exit(1)
+
+ c27_lmgr326b_cc = c27.joinpath("LMGR326B.dll")
+ if not c27_lmgr326b_cc.exists() or not c27_lmgr326b_cc.is_file():
+ LOG.debug(f"copy: '{c27_lmgr326b}', to: '{c27_lmgr326b_cc}'")
+ shutil.copy(c27_lmgr326b, c27_lmgr326b_cc)
+
+ c27_mwcceppc = c27.joinpath("mwcceppc.exe")
+ if not c27_mwcceppc.exists() or not c27_mwcceppc.is_file():
+ LOG.error(
+ (
+ f"Unable to find 'mwcceppc.exe' in '{c27}': missing file '{c27_mwcceppc}'\n"
+ f"Check the README for instructions on how to obtain the compilers"
+ )
+ )
+ sys.exit(1)
+
+ c27_mwldeppc = c27.joinpath("mwldeppc.exe")
+ if not c27_mwldeppc.exists() or not c27_mwldeppc.is_file():
+ LOG.error(
+ (
+ f"Unable to find 'mwldeppc.exe' in '{c27}': missing file '{c27_mwldeppc}'\n"
+ f"Check the README for instructions on how to obtain the compilers"
+ )
+ )
+ sys.exit(1)
+
+ MWCCEPPC_SHA1 = "100DD3A2898A1ECE55462801D42FF5DE8B42E29F"
+ MWCCEPPC_PATCHED_SHA1 = "22C7DCFBAD7FE0710C84EA714B81C5220D8E2996"
+ MWLDEPPC_SHA1 = "8225A47FF099A3AECB32CD307A95CB69B30A6A84"
+
+ with c27_mwcceppc.open("rb") as file:
+ mwcceppc_sha1 = sha1_from_data(bytearray(file.read()))
+
+ with c27_mwldeppc.open("rb") as file:
+ mwldeppc_sha1 = sha1_from_data(bytearray(file.read()))
+
+ c27_mwcceppc_old = c27.joinpath("mwcceppc.old.exe")
+ c27_mwcceppc_orignal = c27.joinpath("mwcceppc.exe")
+ c27_mwcceppc_patched = c27.joinpath("mwcceppc_patched.exe")
+
+ def patch_compiler(src, dst, apply):
+ with src.open("rb") as src_file:
+ with dst.open("wb") as dst_file:
+ data = bytearray(src_file.read())
+ if apply:
+ data[0x001C6A54] = 0x6D
+ else:
+ data[0x001C6A54] = 0x69
+ dst_file.write(data)
+
+ if mwcceppc_sha1 == MWCCEPPC_SHA1:
+ LOG.debug(f"found original compiler: '{c27_mwcceppc}' ('{mwcceppc_sha1}')")
+ c27_mwcceppc_old.unlink(missing_ok=True)
+ shutil.move(c27_mwcceppc, c27_mwcceppc_old)
+ shutil.copy(c27_mwcceppc_old, c27_mwcceppc_orignal)
+ patch_compiler(c27_mwcceppc_old, c27_mwcceppc_patched, apply=True) # patch
+ elif mwcceppc_sha1 == MWCCEPPC_PATCHED_SHA1:
+ LOG.debug(f"found patched compiler: '{c27_mwcceppc}' ('{mwcceppc_sha1}')")
+ c27_mwcceppc_old.unlink(missing_ok=True)
+ shutil.move(c27_mwcceppc, c27_mwcceppc_old)
+ shutil.copy(c27_mwcceppc_old, c27_mwcceppc_patched)
+ patch_compiler(
+ c27_mwcceppc_old, c27_mwcceppc_orignal, apply=False
+ ) # revert patch
+ else:
+ LOG.error(
+ (
+ f"Invalid 'mwcceppc.exe' checksum of '{mwcceppc_sha1}'\n"
+ f"Check the README for instructions on how to obtain the compilers"
+ )
+ )
+ sys.exit(1)
+
+ #
+ text = Text("--- Extracting game assets")
+ text.stylize("bold magenta")
+ CONSOLE.print(text)
+
+ iso = Path("gz2e01.iso")
+ if not iso.exists() or not iso.is_file():
+ LOG.error(
+ (
+ f"Missing file '{iso}'.\n"
+ f"Did you forget to copy the NTSC-U version in the root directory?"
+ )
+ )
+ sys.exit(1)
+
+ try:
+ previous_dir = os.getcwd()
+ os.chdir(str(game_path.absolute()))
+ extract_game_assets.extract("../" + str(iso))
+ os.chdir(previous_dir)
+ except e as Exception:
+ LOG.error(f"failure:")
+ LOG.error(e)
+ sys.exit(1)
+
+ text = Text("--- Complete")
+ text.stylize("bold magenta")
+ CONSOLE.print(text)
+ CONSOLE.print("You should now be able to build the project 👍")
+ CONSOLE.print("Check the README for instructions for further instructions")
+
+
+#
+# Progress
+#
@tp.command(name="progress")
-@click.option('--debug/--no-debug')
-@click.option('--matching/--no-matching', default=True, is_flag=True)
-@click.option('--print-rels', default=False, is_flag=True)
-@click.option('--format', '-f', default="FANCY", type=click.Choice(['FANCY', 'CSV', 'JSON-SHIELD'], case_sensitive=False))
-def progress(debug, matching, format, print_rels):
- """ Calculate decompilation progress """
+@click.option("--debug/--no-debug")
+@click.option("--matching/--no-matching", default=True, is_flag=True)
+@click.option("--print-rels", default=False, is_flag=True)
+@click.option(
+ "--format",
+ "-f",
+ default="FANCY",
+ type=click.Choice(["FANCY", "CSV", "JSON-SHIELD", "JSON"], case_sensitive=False),
+)
+@click.option(
+ "--build-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_BUILD_PATH,
+ required=True,
+)
+def progress(debug, matching, format, print_rels, build_path):
+ """Calculate decompilation progress"""
if debug:
LOG.setLevel(logging.DEBUG)
@@ -82,14 +319,29 @@ def progress(debug, matching, format, print_rels):
text.stylize("bold magenta")
CONSOLE.print(text)
- calculate_progress(matching, format, print_rels)
+ calculate_progress(build_path, matching, format, print_rels)
+
+#
+# Check
+#
@tp.command(name="check")
-@click.option('--debug/--no-debug')
-@click.option('--game-path', default=DEFAULT_GAME_PATH, required=True)
-@click.option('--build-path', default=DEFAULT_BUILD_PATH, required=True)
-def check(debug, game_path, build_path):
- """ Compare SHA1 Checksums """
+@click.option("--debug/--no-debug")
+@click.option("--rels", default=False, is_flag=True)
+@click.option(
+ "--game-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_GAME_PATH,
+ required=True,
+)
+@click.option(
+ "--build-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_BUILD_PATH,
+ required=True,
+)
+def check(debug, rels, game_path, build_path):
+ """Compare SHA1 Checksums"""
if debug:
LOG.setLevel(logging.DEBUG)
@@ -99,7 +351,7 @@ def check(debug, game_path, build_path):
CONSOLE.print(text)
try:
- check_sha1(game_path, build_path)
+ check_sha1(game_path, build_path, rels)
text = Text(" OK")
text.stylize("bold green")
CONSOLE.print(text)
@@ -110,6 +362,7 @@ def check(debug, game_path, build_path):
CONSOLE.print(text)
sys.exit(1)
+
@dataclass
class ProgressSection:
name: str
@@ -121,6 +374,7 @@ class ProgressSection:
def percentage(self):
return 100 * (self.decompiled / self.size)
+
@dataclass
class ProgressGroup:
name: str
@@ -132,31 +386,45 @@ class ProgressGroup:
def percentage(self):
return 100 * (self.decompiled / self.size)
-def calculate_rel_progress(matching, format):
- asm_files = find_used_asm_files(not matching, use_progress_bar=(format == "FANCY"))
+def calculate_rel_progress(build_path, matching, format, asm_files, ranges):
results = []
- rel_paths = get_files_with_ext(Path("build/dolzel2/rel/"), ".rel")
+ start = time.time()
+ rel_paths = get_files_with_ext(build_path.joinpath("rel"), ".rel")
+ end = time.time()
+ LOG.debug(f"get_files_with_ext: {(end - start)*1000} ms")
+
+ start = time.time()
+ from collections import defaultdict
+
+ range_dict = defaultdict(list)
+ for file, range in zip(asm_files, ranges):
+ str_file = str(file)
+ if not str_file.startswith("asm/rel/"):
+ continue
+ rel = str_file.split("/")[-2]
+ range_dict[rel].append(range[1] - range[0])
+
+ end = time.time()
+ LOG.debug(f"range_dict: {(end - start)*1000} ms")
+
for rel_path in rel_paths:
- with rel_path.open("rb") as file:
- data = file.read()
+ start = time.time()
+ size = rel_path.stat().st_size
name = rel_path.name.replace(".rel", "")
- size = len(data)
- rel_asm_files = [ file for file in asm_files if f"/{name}/" in str(file)]
- ranges = find_function_ranges(rel_asm_files)
-
- decompiled = size
- for range in ranges:
- decompiled -= (range[1] - range[0])
+ end = time.time()
+ rel_ranges = range_dict[name]
+ decompiled = size - sum(rel_ranges)
results.append(ProgressGroup(name, size, decompiled, {}))
return results
-def calculate_dol_progress(matching, format):
+
+def calculate_dol_progress(build_path, matching, format, asm_files, ranges):
# read .dol file
- dol_path = Path("build/dolzel2/main.dol")
+ dol_path = build_path.joinpath("main.dol")
if not dol_path.exists():
LOG.error(f"Unable to read '{dol_path}'")
sys.exit(1)
@@ -170,26 +438,27 @@ def calculate_dol_progress(matching, format):
format_size = 0x100
# assume everything is decompiled
- sections = dict([
- (section.name, ProgressSection(section.name, section.addr, section.aligned_size, section.aligned_size))
- for section in dol.sections
- if section.data
- ])
-
+ sections = dict(
+ [
+ (
+ section.name,
+ ProgressSection(
+ section.name,
+ section.addr,
+ section.aligned_size,
+ section.aligned_size,
+ ),
+ )
+ for section in dol.sections
+ if section.data
+ ]
+ )
init = dol.get_named_section(".init")
assert init
- init_decompiled_size = init.size
text = dol.get_named_section(".text")
assert text
- text_decompiled_size = text.size
-
- # find all _used_ asm files
- asm_files = find_used_asm_files(not matching, use_progress_bar=(format == "FANCY"))
-
- # calculate the range each asm file occupies
- ranges = find_function_ranges(asm_files)
LOG.debug(f"init {init.addr:08X}-{init.addr + init.size:08X}")
LOG.debug(f"text {text.addr:08X}-{text.addr + text.size:08X}")
@@ -197,84 +466,141 @@ def calculate_dol_progress(matching, format):
# substract the size of each asm function
for function_range in ranges:
if function_range[0] >= init.addr and function_range[1] < init.addr + init.size:
- sections[".init"].decompiled -= (function_range[1] - function_range[0])
- elif function_range[0] >= text.addr and function_range[1] < text.addr + text.size:
- sections[".text"].decompiled-= (function_range[1] - function_range[0])
+ sections[".init"].decompiled -= function_range[1] - function_range[0]
+ elif (
+ function_range[0] >= text.addr and function_range[1] < text.addr + text.size
+ ):
+ sections[".text"].decompiled -= function_range[1] - function_range[0]
+
+ total_decompiled_size = format_size + sum(
+ [section.decompiled for section in sections.values()]
+ )
- total_decompiled_size = format_size + sum([section.decompiled for section in sections.values()])
return ProgressGroup("main.dol", total_size, total_decompiled_size, sections)
-def calculate_progress(matching, format, print_rels):
+
+def calculate_progress(build_path, matching, format, print_rels):
if not matching:
LOG.error("non-matching progress is not support yet.")
sys.exit(1)
- dol_progress = calculate_dol_progress(matching, format)
- rels_progress = calculate_rel_progress(matching, format)
+ start = time.time()
+ # find all _used_ asm files
+ asm_files = find_used_asm_files(not matching, use_progress_bar=(format == "FANCY"))
+ end = time.time()
+ LOG.debug(f"find_used_asm_files: {(end - start)*1000} ms")
+
+ start = time.time()
+ # calculate the range each asm file occupies
+ ranges = find_function_ranges(asm_files)
+ end = time.time()
+ LOG.debug(f"find_function_ranges: {(end - start)*1000} ms")
+
+ start = time.time()
+ dol_progress = calculate_dol_progress(
+ build_path, matching, format, asm_files, ranges
+ )
+ end = time.time()
+ LOG.debug(f"calculate_dol_progress: {(end - start)*1000} ms")
rel_size = 0
rel_decompiled = 0
- for rel in rels_progress:
- rel_size += rel.size
- rel_decompiled += rel.decompiled
+ rels_progress = []
+ if print_rels:
+ start = time.time()
+ rels_progress = calculate_rel_progress(
+ build_path, matching, format, asm_files, ranges
+ )
+ end = time.time()
+ LOG.debug(f"calculate_rel_progress: {(end - start)*1000} ms")
+
+ for rel in rels_progress:
+ rel_size += rel.size
+ rel_decompiled += rel.decompiled
total_size = dol_progress.size + rel_size
decompiled_size = dol_progress.decompiled + rel_decompiled
-
if format == "FANCY":
table = Table(title="main.dol")
- table.add_column("Section", justify="right",
- style="cyan", no_wrap=True)
+ table.add_column("Section", justify="right", style="cyan", no_wrap=True)
table.add_column("Percentage", style="green")
- table.add_column("Decompiled (bytes)",
- justify="right", style="bright_yellow")
- table.add_column("Total (bytes)", justify="right",
- style="bright_magenta")
+ table.add_column("Decompiled (bytes)", justify="right", style="bright_yellow")
+ table.add_column("Total (bytes)", justify="right", style="bright_magenta")
for name, section in dol_progress.sections.items():
- table.add_row(name, f"{section.percentage:10.6f}%", f"{section.decompiled}", f"{section.size}")
+ table.add_row(
+ name,
+ f"{section.percentage:10.6f}%",
+ f"{section.decompiled}",
+ f"{section.size}",
+ )
table.add_row("", "", "", "")
- table.add_row("total", f"{dol_progress.percentage:10.6f}%", f"{dol_progress.decompiled}", f"{dol_progress.size}")
+ table.add_row(
+ "total",
+ f"{dol_progress.percentage:10.6f}%",
+ f"{dol_progress.decompiled}",
+ f"{dol_progress.size}",
+ )
CONSOLE.print(table)
if print_rels:
table = Table(title="RELs")
- table.add_column("Section", justify="right",
- style="cyan", no_wrap=True)
+ table.add_column("Section", justify="right", style="cyan", no_wrap=True)
table.add_column("Percentage", style="green")
- table.add_column("Decompiled (bytes)",
- justify="right", style="bright_yellow")
- table.add_column("Total (bytes)", justify="right",
- style="bright_magenta")
+ table.add_column(
+ "Decompiled (bytes)", justify="right", style="bright_yellow"
+ )
+ table.add_column("Total (bytes)", justify="right", style="bright_magenta")
for rel in rels_progress:
- table.add_row(rel.name, f"{rel.percentage:10.6f}%", f"{rel.decompiled}", f"{rel.size}")
+ table.add_row(
+ rel.name,
+ f"{rel.percentage:10.6f}%",
+ f"{rel.decompiled}",
+ f"{rel.size}",
+ )
table.add_row("", "", "", "")
- table.add_row("total", f"{100 * (rel_decompiled / rel_size):10.6f}%", f"{rel_decompiled}", f"{rel_size}")
+ table.add_row(
+ "total",
+ f"{100 * (rel_decompiled / rel_size):10.6f}%",
+ f"{rel_decompiled}",
+ f"{rel_size}",
+ )
CONSOLE.print(table)
table = Table(title="Total")
- table.add_column("Section", justify="right",
- style="cyan", no_wrap=True)
+ table.add_column("Section", justify="right", style="cyan", no_wrap=True)
table.add_column("Percentage", style="green")
- table.add_column("Decompiled (bytes)",
- justify="right", style="bright_yellow")
- table.add_column("Total (bytes)", justify="right",
- style="bright_magenta")
-
- table.add_row("main.dol", f"{dol_progress.percentage:10.6f}%", f"{dol_progress.decompiled}", f"{dol_progress.size}")
+ table.add_column("Decompiled (bytes)", justify="right", style="bright_yellow")
+ table.add_column("Total (bytes)", justify="right", style="bright_magenta")
+
+ table.add_row(
+ "main.dol",
+ f"{dol_progress.percentage:10.6f}%",
+ f"{dol_progress.decompiled}",
+ f"{dol_progress.size}",
+ )
if rels_progress:
- table.add_row("RELs", f"{100 * (rel_decompiled / rel_size):10.6f}%", f"{rel_decompiled}", f"{rel_size}")
+ table.add_row(
+ "RELs",
+ f"{100 * (rel_decompiled / rel_size):10.6f}%",
+ f"{rel_decompiled}",
+ f"{rel_size}",
+ )
else:
# if we don't have any rel progress, just indicate N/A
table.add_row("RELs", "–".center(11), f"–", f"–")
-
table.add_row("", "", "", "")
- table.add_row("total", f"{100 * (decompiled_size / total_size):10.6f}%", f"{decompiled_size}", f"{total_size}")
+ table.add_row(
+ "total",
+ f"{100 * (decompiled_size / total_size):10.6f}%",
+ f"{decompiled_size}",
+ f"{total_size}",
+ )
CONSOLE.print(table)
elif format == "CSV":
version = 1
@@ -283,68 +609,165 @@ def calculate_progress(matching, format, print_rels):
git_hash = git_object.hexsha
data = [
- str(version), timestamp, git_hash,
- str(dol_progress.decompiled), str(dol_progress.size),
- str(rel_decompiled), str(rel_size),
- str(decompiled_size), str(total_size),
-
+ str(version),
+ timestamp,
+ git_hash,
+ str(dol_progress.decompiled),
+ str(dol_progress.size),
+ str(rel_decompiled),
+ str(rel_size),
+ str(decompiled_size),
+ str(total_size),
]
print(",".join(data))
elif format == "JSON-SHIELD":
# https://shields.io/endpoint
- print(json.dumps({
- "schemaVersion": 1,
- "label": "progress",
- "message": f"{100 * (decompiled_size / total_size):.3g}%",
- "color": 'yellow',
- }))
+ print(
+ json.dumps(
+ {
+ "schemaVersion": 1,
+ "label": "progress",
+ "message": f"{100 * (decompiled_size / total_size):.3g}%",
+ "color": "yellow", # TODO: color
+ }
+ )
+ )
+ elif format == "JSON":
+ matching = {}
+ non_matching = {}
+
+ matching["main.dol"] = {
+ "decompiled": dol_progress.decompiled,
+ "total": dol_progress.size,
+ "sections": [
+ {
+ name: {
+ "decompiled": sec.decompiled,
+ "total": sec.size,
+ }
+ }
+ for name, sec in dol_progress.sections.items()
+ ],
+ }
+
+ if rels_progress:
+ matching["rels"] = {
+ "decompiled": rel_decompiled,
+ "total": rel_size,
+ }
+
+ for rel in rels_progress:
+ matching[rel.name] = {
+ "decompiled": rel.decompiled,
+ "total": rel.size,
+ }
+
+ print(
+ json.dumps(
+ {
+ "matching": matching,
+ "non-matchgin": non_matching,
+ }
+ )
+ )
else:
print(dol_progress.percentage)
print(100 * (rel_decompiled / rel_size))
print(100 * (decompiled_size / total_size))
LOG.error("unknown format: '{format}'")
+
+def find_function_range(asm):
+ with asm.open("r") as file:
+ lines = file.readlines()
+ for line in lines:
+ if line.startswith("/* "):
+ fast_first = int(line[3:11], 16)
+ break
+
+ for line in lines[::-1]:
+ if line.startswith("/* "):
+ fast_last = int(line[3:11], 16) + 4
+ break
+
+ return (fast_first, fast_last)
+
+
def find_function_ranges(asm_files):
- function_ranges = []
- for asm in asm_files:
- with asm.open('r') as file:
- first = None
- last = None
- for line in file.readlines():
- if not line.startswith('/* '):
- continue
- addr = int(line[3:11], 16)
- if not first:
- first = addr
- last = addr + 4
+ if len(asm_files) < 128:
+ return [find_function_range(x) for x in asm_files]
- function_ranges.append((first, last))
+ thread_count = 4
+ with mp.Pool(processes=2 * thread_count) as pool:
+ jobs_left = len(asm_files)
+ result = pool.map_async(find_function_range, asm_files)
+ while result._number_left > 0:
+ time.sleep(1 / 20)
- return function_ranges
+ function_ranges = result.get()
+ return function_ranges
-@tp.command(name="remove-unused-asm", help="remove all of the asm that is decompiled and not used anymore")
-def remove_unused_asm_cmd():
- remove_unused_asm()
-def remove_unused_asm():
- unused_files, error_files = find_unused_asm_files(False)
+#
+# Remove ASM
+#
+@tp.command(
+ name="remove-unused-asm",
+ help="Remove all of the asm that is decompiled and not used anymore",
+)
+@click.option("--check", default=False, is_flag=True)
+def remove_unused_asm_cmd(check):
+ result = remove_unused_asm(check)
+ if check:
+ if result == 0:
+ print("OK")
+ sys.exit(0)
+ else:
+ sys.exit(1)
- for unused_file in unused_files:
- unused_file.unlink()
- CONSOLE.print(f"removed '{unused_file}'")
- text = Text(" OK")
- text.stylize("bold green")
- CONSOLE.print(text)
+def remove_unused_asm(check):
+ unused_files, error_files = find_unused_asm_files(False, use_progress_bar=not check)
+ if not check:
+ for unused_file in unused_files:
+ unused_file.unlink()
+ CONSOLE.print(f"removed '{unused_file}'")
+
+ text = Text(" OK")
+ text.stylize("bold green")
+ CONSOLE.print(text)
+
+ return len(unused_files)
+
+
+#
+# Format
+#
@tp.command(name="format")
-@click.option('--debug/--no-debug')
-@click.option('--thread-count', '-j', 'thread_count', help="This option is passed forward to all 'make' commands.", default=4)
-@click.option('--game-path', default=DEFAULT_GAME_PATH, required=True)
-@click.option('--build-path', default=DEFAULT_BUILD_PATH, required=True)
+@click.option("--debug/--no-debug")
+@click.option(
+ "--thread-count",
+ "-j",
+ "thread_count",
+ help="This option is passed forward to all 'make' commands.",
+ default=4,
+)
+@click.option(
+ "--game-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_GAME_PATH,
+ required=True,
+)
+@click.option(
+ "--build-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_BUILD_PATH,
+ required=True,
+)
def format(debug, thread_count, game_path, build_path):
- """ Format all .cpp/.h files using clang-format """
-
+ """Format all .cpp/.h files using clang-format"""
+
if debug:
LOG.setLevel(logging.DEBUG)
@@ -362,13 +785,39 @@ def format(debug, thread_count, game_path, build_path):
CONSOLE.print(text)
sys.exit(1)
+
+#
+# Pull-Request
+#
@tp.command(name="pull-request")
-@click.option('--debug/--no-debug')
-@click.option('--thread-count', '-j', 'thread_count', help="This option is passed forward to all 'make' commands.", default=4)
-@click.option('--game-path', default=DEFAULT_GAME_PATH, required=True)
-@click.option('--build-path', default=DEFAULT_BUILD_PATH, required=True)
-def pull_request(debug, thread_count, game_path, build_path):
- """ Verify that everything is OK before pull-request """
+@click.option("--debug/--no-debug")
+@click.option(
+ "--rels",
+ default=False,
+ is_flag=True,
+ help="RELs will also be build and checked",
+)
+@click.option(
+ "--thread-count",
+ "-j",
+ "thread_count",
+ help="This option is passed forward to all 'make' commands.",
+ default=4,
+)
+@click.option(
+ "--game-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_GAME_PATH,
+ required=True,
+)
+@click.option(
+ "--build-path",
+ type=PathPath(file_okay=False, dir_okay=True),
+ default=DEFAULT_BUILD_PATH,
+ required=True,
+)
+def pull_request(debug, rels, thread_count, game_path, build_path):
+ """Verify that everything is OK before pull-request"""
if debug:
LOG.setLevel(logging.DEBUG)
@@ -377,7 +826,12 @@ def pull_request(debug, thread_count, game_path, build_path):
text.stylize("bold")
CONSOLE.print(text)
- remove_unused_asm()
+ #
+ text = Text("--- Remove unused .s files")
+ text.stylize("bold magenta")
+ CONSOLE.print(text)
+
+ remove_unused_asm(False)
#
text = Text("--- Clang-Format")
@@ -399,7 +853,7 @@ def pull_request(debug, thread_count, game_path, build_path):
text.stylize("bold magenta")
CONSOLE.print(text)
- if rebuild(thread_count):
+ if rebuild(thread_count, rels):
text = Text(" OK")
text.stylize("bold green")
CONSOLE.print(text)
@@ -415,7 +869,7 @@ def pull_request(debug, thread_count, game_path, build_path):
CONSOLE.print(text)
try:
- check_sha1(game_path, build_path)
+ check_sha1(game_path, build_path, rels)
text = Text(" OK")
text.stylize("bold green")
CONSOLE.print(text)
@@ -431,11 +885,11 @@ def pull_request(debug, thread_count, game_path, build_path):
text.stylize("bold magenta")
CONSOLE.print(text)
- calculate_progress(True, "FANCY", False)
+ calculate_progress(build_path, True, "FANCY", rels)
def find_all_asm_files():
- """ Recursivly find all files in the 'asm/' folder """
+ """Recursivly find all files in the 'asm/' folder"""
files = set()
errors = set()
@@ -451,7 +905,7 @@ def find_all_asm_files():
if path.is_dir():
recursive(path)
else:
- if path.suffix == '.s':
+ if path.suffix == ".s":
files.add(path)
else:
errors.add(path)
@@ -462,25 +916,27 @@ def find_all_asm_files():
recursive(root)
LOG.debug(
- f"find_all_asm_files: found {len(files)} .s files and {len(errors)} bad files")
+ f"find_all_asm_files: found {len(files)} .s files and {len(errors)} bad files"
+ )
return files, errors
-def find_unused_asm_files(non_matching):
- """ Search for unused asm function files. """
+def find_unused_asm_files(non_matching, use_progress_bar=True):
+ """Search for unused asm function files."""
asm_files, error_files = find_all_asm_files()
- included_asm_files = find_used_asm_files(non_matching)
+ included_asm_files = find_used_asm_files(
+ non_matching, use_progress_bar=use_progress_bar
+ )
unused_asm_files = asm_files - included_asm_files
- LOG.debug(
- f"find_unused_asm_files: found {len(unused_asm_files)} unused .s files")
+ LOG.debug(f"find_unused_asm_files: found {len(unused_asm_files)} unused .s files")
return unused_asm_files, error_files
def find_all_header_files():
- """ Recursivly find all files in the 'include/' folder """
+ """Recursivly find all files in the 'include/' folder"""
files = set()
@@ -496,7 +952,7 @@ def find_all_header_files():
if path.is_dir():
recursive(path)
else:
- if path.suffix == '.h':
+ if path.suffix == ".h":
files.add(path)
root = Path("./include/")
@@ -508,7 +964,7 @@ def find_all_header_files():
def find_all_cpp_files():
- """ Recursivly find all files in the 'cpp/' folder """
+ """Recursivly find all files in the 'cpp/' folder"""
files = set()
@@ -524,7 +980,7 @@ def find_all_cpp_files():
if path.is_dir():
recursive(path)
else:
- if path.suffix == '.cpp':
+ if path.suffix == ".cpp":
files.add(path)
src_root = Path("./src/")
@@ -568,7 +1024,9 @@ def find_used_asm_files(non_matching, use_progress_bar=True):
includes = set()
if use_progress_bar:
- with Progress(console=CONSOLE, transient=True, refresh_per_second=1) as progress:
+ with Progress(
+ console=CONSOLE, transient=True, refresh_per_second=1
+ ) as progress:
task = progress.add_task(f"preprocessing...", total=len(cpp_files))
for cpp_file in cpp_files:
@@ -589,8 +1047,7 @@ def find_used_asm_files(non_matching, use_progress_bar=True):
def clang_format_impl(file):
cmd = ["clang-format", "-i", str(file)]
- cf = subprocess.run(args=cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ cf = subprocess.run(args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
def clang_format(thread_count):
@@ -602,7 +1059,9 @@ def clang_format(thread_count):
with mp.Pool(processes=2 * thread_count) as pool:
result = pool.map_async(clang_format_impl, files)
jobs_left = len(files)
- with Progress(console=CONSOLE, transient=True, refresh_per_second=5) as progress:
+ with Progress(
+ console=CONSOLE, transient=True, refresh_per_second=5
+ ) as progress:
task = progress.add_task(f"clang-formating...", total=len(files))
while result._number_left > 0:
@@ -610,46 +1069,71 @@ def clang_format(thread_count):
change = jobs_left - left
jobs_left = left
progress.update(
- task, description=f"clang-formating... ({left} left)", advance=change)
- time.sleep(1/5)
+ task,
+ description=f"clang-formating... ({left} left)",
+ advance=change,
+ )
+ time.sleep(1 / 5)
progress.update(task, advance=jobs_left)
return True
-def rebuild(thread_count):
+
+def rebuild(thread_count, include_rels):
LOG.debug("make clean")
with Progress(console=CONSOLE, transient=True, refresh_per_second=5) as progress:
task = progress.add_task(f"make clean", total=1000, start=False)
cmd = ["make", f"-j{thread_count}", "clean"]
- result = subprocess.run(args=cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ result = subprocess.run(
+ args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
LOG.debug("make clean complete")
if result.returncode != 0:
return False
+ if include_rels:
+ LOG.debug("make clean_rels")
+ with Progress(
+ console=CONSOLE, transient=True, refresh_per_second=5
+ ) as progress:
+ task = progress.add_task(f"make clean_rels", total=1000, start=False)
+
+ cmd = ["make", f"-j{thread_count}", "clean_rels"]
+ result = subprocess.run(
+ args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ LOG.debug("make clean_rels complete")
+ if result.returncode != 0:
+ return False
+
LOG.debug("make main.dol")
with Progress(console=CONSOLE, transient=True, refresh_per_second=5) as progress:
task = progress.add_task(f"make", total=1000, start=False)
cmd = ["make", f"-j{thread_count}", "build/dolzel2/main.dol"]
- result = subprocess.run(args=cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
+ result = subprocess.run(
+ args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
LOG.debug("make main.dol complete")
if result.returncode != 0:
return False
- LOG.debug("make RELs")
- with Progress(console=CONSOLE, transient=True, refresh_per_second=5) as progress:
- task = progress.add_task(f"make rels", total=1000, start=False)
-
- cmd = ["make", f"-j{thread_count}", "rels"]
- result = subprocess.run(args=cmd, stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- LOG.debug("make RELs complete")
- if result.returncode != 0:
- return False
+ if include_rels:
+ LOG.debug("make RELs")
+ with Progress(
+ console=CONSOLE, transient=True, refresh_per_second=5
+ ) as progress:
+ task = progress.add_task(f"make rels", total=1000, start=False)
+
+ cmd = ["make", f"-j{thread_count}", "rels"]
+ result = subprocess.run(
+ args=cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ LOG.debug("make RELs complete")
+ if result.returncode != 0:
+ return False
return True
@@ -660,65 +1144,73 @@ def sha1_from_data(data):
return sha1.hexdigest().upper()
+
def get_files_with_ext(path, ext):
return [x for x in path.glob(f"**/*{ext}") if x.is_file()]
+
class CheckException(Exception):
...
-def check_sha1(game_path, build_path):
-
- #dol_path = game_path.joinpath("main.dol")
- #if not dol_path.exists():
- # raise CheckException(f"File not found: '{dol_path}'")
- rel_path = game_path.joinpath("rel/Final/Release")
- if not rel_path.exists():
- raise CheckException(f"Path not found: '{rel_path}'")
+def check_sha1(game_path, build_path, include_rels):
+ if include_rels:
+ rel_path = game_path.joinpath("rel/Final/Release")
+ if not rel_path.exists():
+ raise CheckException(f"Path not found: '{rel_path}'")
- rels_path = get_files_with_ext(rel_path, ".rel")
- rels_archive_path = game_path.joinpath("RELS.arc")
- if not rels_archive_path.exists():
- raise CheckException(f"File not found: '{rels_archive_path}'")
+ rels_path = get_files_with_ext(rel_path, ".rel")
+ rels_archive_path = game_path.joinpath("RELS.arc")
+ if not rels_archive_path.exists():
+ raise CheckException(f"File not found: '{rels_archive_path}'")
- #LOG.debug(f"DOL Path: '{dol_path}'")
- LOG.debug(f"RELs Path: '{rel_path}' (found {len(rels_path)} RELs)")
- LOG.debug(f"RELs Archive Path: '{rels_archive_path}'")
+ LOG.debug(f"RELs Path: '{rel_path}' (found {len(rels_path)} RELs)")
+ LOG.debug(f"RELs Archive Path: '{rels_archive_path}'")
EXPECTED = {}
- #with dol_path.open('rb') as file:
- # data = file.read()
- # EXPECTED[0] = (str(dol_path), sha1_from_data(data),sha1_from_data(data),)
- EXPECTED[0] = ("", "4997D93B9692620C40E90374A0F1DBF0E4889395", "4997D93B9692620C40E90374A0F1DBF0E4889395",)
-
- for rel_filepath in rels_path:
- with rel_filepath.open('rb') as file:
- data = bytearray(file.read())
- yaz0_data = data
- if struct.unpack('>I', data[:4])[0] == 0x59617A30:
- data = yaz0.decompress(io.BytesIO(data))
-
- rel = librel.read(data)
- EXPECTED[rel.index] = (str(rel_filepath), sha1_from_data(yaz0_data),sha1_from_data(data),)
-
- with rels_archive_path.open('rb') as file:
- rarc = libarc.read(file.read())
- for depth, file in rarc.files_and_folders:
- if not isinstance(file, libarc.File):
- continue
-
- if file.name.endswith(".rel"):
- data = file.data
+ EXPECTED[0] = (
+ "",
+ "4997D93B9692620C40E90374A0F1DBF0E4889395",
+ "4997D93B9692620C40E90374A0F1DBF0E4889395",
+ )
+
+ if include_rels:
+ for rel_filepath in rels_path:
+ with rel_filepath.open("rb") as file:
+ data = bytearray(file.read())
yaz0_data = data
- if struct.unpack('>I', data[:4])[0] == 0x59617A30:
+ if struct.unpack(">I", data[:4])[0] == 0x59617A30:
data = yaz0.decompress(io.BytesIO(data))
- xxx_path = Path('build').joinpath(file.name)
- with xxx_path.open('wb') as write_file:
- write_file.write(data)
-
rel = librel.read(data)
- EXPECTED[rel.index] = (file.name, sha1_from_data(yaz0_data),sha1_from_data(data),)
+ EXPECTED[rel.index] = (
+ str(rel_filepath),
+ sha1_from_data(yaz0_data),
+ sha1_from_data(data),
+ )
+
+ with rels_archive_path.open("rb") as file:
+ rarc = libarc.read(file.read())
+ for depth, file in rarc.files_and_folders:
+ if not isinstance(file, libarc.File):
+ continue
+
+ if file.name.endswith(".rel"):
+ data = file.data
+ yaz0_data = data
+ if struct.unpack(">I", data[:4])[0] == 0x59617A30:
+ data = yaz0.decompress(io.BytesIO(data))
+
+ xxx_path = Path("build").joinpath(file.name)
+ with xxx_path.open("wb") as write_file:
+ write_file.write(data)
+
+ rel = librel.read(data)
+ EXPECTED[rel.index] = (
+ file.name,
+ sha1_from_data(yaz0_data),
+ sha1_from_data(data),
+ )
if not build_path.exists():
raise CheckException(f"Path not found: '{build_path}'")
@@ -727,28 +1219,38 @@ def check_sha1(game_path, build_path):
if not build_dol_path.exists():
raise CheckException(f"File not found: '{build_dol_path}'")
- build_rels_path = get_files_with_ext(build_path, ".rel")
-
CURRENT = {}
- with build_dol_path.open('rb') as file:
+ with build_dol_path.open("rb") as file:
data = file.read()
- CURRENT[0] = (str(build_dol_path), sha1_from_data(data),sha1_from_data(data),)
+ CURRENT[0] = (
+ str(build_dol_path),
+ sha1_from_data(data),
+ sha1_from_data(data),
+ )
- for rel_filepath in build_rels_path:
- with rel_filepath.open('rb') as file:
- data = bytearray(file.read())
- yaz0_data = data
- if struct.unpack('>I', data[:4])[0] == 0x59617A30:
- data = yaz0.decompress(io.BytesIO(data))
+ if include_rels:
+ build_rels_path = get_files_with_ext(build_path, ".rel")
+ for rel_filepath in build_rels_path:
+ with rel_filepath.open("rb") as file:
+ data = bytearray(file.read())
+ yaz0_data = data
+ if struct.unpack(">I", data[:4])[0] == 0x59617A30:
+ data = yaz0.decompress(io.BytesIO(data))
- rel = librel.read(data)
- CURRENT[rel.index] = (str(rel_filepath), sha1_from_data(yaz0_data),sha1_from_data(data),)
+ rel = librel.read(data)
+ CURRENT[rel.index] = (
+ str(rel_filepath),
+ sha1_from_data(yaz0_data),
+ sha1_from_data(data),
+ )
expected_keys = set(EXPECTED.keys())
current_keys = set(CURRENT.keys())
match = expected_keys - current_keys
if len(match) > 0:
- raise CheckException(f"Missing RELs (expected: {len(expected_keys)}, found: {len(current_keys)})")
+ raise CheckException(
+ f"Missing main.dol or RELs (expected: {len(expected_keys)}, found: {len(current_keys)})"
+ )
errors = 0
for key in expected_keys:
diff --git a/tools/vtables.py b/tools/vtables.py
deleted file mode 100644
index 2212c456ac..0000000000
--- a/tools/vtables.py
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/env python3
-
-BANNER = """
-# This script is will go through the frameworkF.map and extract
-# all vtables and their location. This information can be used to
-# place the compiler generated vtables in their correct place.
-# - Julgodis, 2020
-"""
-
-def extract():
- file = open('frameworkF.map', 'r')
- lines = file.readlines()
- output = open('vtable.lcf', 'w')
-
- for line in lines:
- data = [ x.strip() for x in line.strip().split(" ") ]
- data = [ x for x in data if len(x) > 0 ]
- if len(data) < 6 or len(data) > 7:
- continue
-
- if not data[4].startswith("__vt"):
- continue
-
- output.write("\"%s\" = %s;\n" % (data[4], "0x" + data[2]))
- output.close()
-
-if __name__ == "__main__":
- import sys
-
- print(BANNER)
- print("...")
- extract()
- print("COMPLETE vtable.lcf") \ No newline at end of file