diff options
| author | Jonathan Wase <jonathan@familjenwase.se> | 2021-12-02 23:38:37 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-02 23:38:37 +0100 |
| commit | bc428f7f65b97cc9035aed1dc1b71c54ff2e6c3d (patch) | |
| tree | a9ee5b38a79336db38dd126bec4b538a9a541bbd /tools | |
| parent | 5f187a0776487afa64cb0993b1e96bb5ee115efd (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/.gitignore | 3 | ||||
| -rw-r--r-- | tools/Makefile | 8 | ||||
| -rw-r--r-- | tools/conflict.py | 438 | ||||
| -rw-r--r-- | tools/difftools.py | 294 | ||||
| -rw-r--r-- | tools/dol2asm.py | 134 | ||||
| -rw-r--r-- | tools/dol2asm_settings.py | 1638 | ||||
| -rw-r--r-- | tools/elf2dol/Makefile | 7 | ||||
| -rw-r--r-- | tools/elf2dol/elf2dol.c (renamed from tools/elf2dol.c) | 0 | ||||
| -rw-r--r-- | tools/extract_game_assets.py | 105 | ||||
| -rw-r--r-- | tools/find_unused_asm.py | 32 | ||||
| -rw-r--r-- | tools/lcf.py | 134 | ||||
| -rw-r--r-- | tools/libdol2asm/__init__.py | 8 | ||||
| -rw-r--r-- | tools/libdol2asm/requirements.txt | 9 | ||||
| -rw-r--r-- | tools/libelf/object.py | 182 | ||||
| -rw-r--r-- | tools/makerel.py | 196 | ||||
| -rw-r--r-- | tools/postprocess.py | 319 | ||||
| -rw-r--r-- | tools/rel.py | 124 | ||||
| -rw-r--r-- | tools/requirements.txt | 12 | ||||
| -rw-r--r-- | tools/section2cpp.py | 612 | ||||
| -rw-r--r-- | tools/splitter/asm_parser.py | 159 | ||||
| -rw-r--r-- | tools/splitter/demangle.py | 311 | ||||
| -rw-r--r-- | tools/splitter/split.py | 338 | ||||
| -rw-r--r-- | tools/splitter/util.py | 17 | ||||
| -rw-r--r-- | tools/tp.py | 1010 | ||||
| -rw-r--r-- | tools/vtables.py | 33 |
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 |
