From 9de4cebb799933e60a70780c5daa6fc68f56f9bb Mon Sep 17 00:00:00 2001 From: Alex Bates Date: Wed, 21 Oct 2020 03:10:13 +0100 Subject: [PATCH] map disassembly --- tools/disasm_map.py | 182 +++++++++++++++++++++++++++++++++++++++++ tools/disasm_script.py | 21 +++-- 2 files changed, 197 insertions(+), 6 deletions(-) create mode 100755 tools/disasm_map.py diff --git a/tools/disasm_map.py b/tools/disasm_map.py new file mode 100755 index 0000000000..a34d4d6c47 --- /dev/null +++ b/tools/disasm_map.py @@ -0,0 +1,182 @@ +#! /usr/bin/python3 + +import sys +import os +import yaml +from struct import unpack +from disasm_script import disassemble as disassemble_script + +def disassemble(bytes, offset, midx, symbol_map = {}, map_name = "map"): + out = "" + found_data = False + + while len(midx) > 0: + struct = midx.pop(0) + name = struct["name"] + if name == "Script_Main": name = f"{map_name}_Main" + + #print(f"{offset:X} ({name}, start = {struct['start']:X}, len = {struct['length']:X})") + + if struct["start"] == offset: + found_data = True + + if struct["start"] != offset: + # end of data / padding + break + + # format struct + if struct["type"].startswith("Script"): + out += disassemble_script(bytes, name, symbol_map) + elif struct["type"] == "Padding": + # nops at end of file + bytes.seek(offset % 4, 1) + return out + elif struct["type"] == "EntryList": + out += f"EntryList {map_name}_entryList = {{" + for i in range(0, struct["length"], 4 * 4): + x,y,z,yaw = unpack(">ffff", bytes.read(4 * 4)) + out += f"\n {{ {x}f, {y}f, {z}f, {yaw}f }}," + out += f"\n}};\n" + elif struct["type"] == "Header": + out += f"MapConfig {map_name}_config = {{\n" + + bytes.read(0x10) + + main,entry_list,entry_count = unpack(">IIi", bytes.read(4 * 3)) + out += f" .main = {map_name}_Main,\n" + out += f" .entryList = {map_name}_entryList,\n" + out += f" .entryCount = {entry_count},\n" + + bytes.read(0x1C) + + bg,tattle = unpack(">II", bytes.read(4 * 2)) + out += f" .background = {'&gBackgroundImage' if bg == 0x80200000 else 'NULL'},\n" + out += f" .tattle = {tattle:X},\n" + + out += f"}};\n" + else: # unknown type of struct + out += f"s32 {name}[] = {{" + for i in range(0, struct["length"], 4): + if (i % 0x20) == 0: + out += f"\n " + + word = int.from_bytes(bytes.read(4), byteorder="big") + + if word in symbol_map: + out += f" {symbol_map[word]}," + else: + out += f" 0x{word:08X}," + + out += f"\n}};\n" + + out += "\n" + elif found_data: + if struct["type"] != "Padding": + # put struct back on list + midx.insert(0, struct) + + # nops at end of file + bytes.seek(offset % 4, 1) + + return out + + if struct["type"] != "Function" and not struct["type"] == "Padding" and not (struct["type"] == "Missing" and not found_data): + offset += struct["length"] + + # end of data + return out + +def parse_midx(file, prefix = ""): + structs = [] + + for line in file.readlines(): + s = line.split("#") + if len(s) == 5: + if s[0] == "$Start": continue + if s[0] == "$End": continue + structs.append({ + "name": prefix + name_struct(s[0]), + "type": s[1], + "start": int(s[2], 16), + "vaddr": int(s[3], 16), + "length": int(s[4], 16), + "end": int(s[2], 16) + int(s[4], 16), + }) + elif "Missing" in s: + start = int(s[1], 16) + end = int(s[2], 16) + vaddr = start + 0x80240000 + structs.append({ + "name": f"{prefix}unk_missing_{vaddr:X}", + "type": "Missing", + "start": start, + "vaddr": vaddr, + "length": end - start, + "end": end, + }) + elif "Padding" in s: + start = int(s[1], 16) + end = int(s[2], 16) + vaddr = start + 0x80240000 + structs.append({ + "name": f"{prefix}__padding__", + "type": "Padding", + "start": start, + "vaddr": vaddr, + "length": end - start, + "end": end, + }) + + structs.sort(key=lambda s: s["start"]) + return structs + +def name_struct(s): + return s[1:].replace("???", "unk") + +if __name__ == "__main__": + if len(sys.argv) == 1: + print("usage: ./disasm_map.py ") + print("Converts split map data into C files using a .midx file from Star Rod.") + exit() + + map_name = os.path.splitext(os.path.basename(sys.argv[1]))[0] + area_name = "area_" + map_name.split("_")[0] + if len(area_name) > 8: + area_name = area_name[:8] + + with open(sys.argv[1], "r") as f: + midx = parse_midx(f, map_name + "_") + + symbol_map = {} + for struct in midx: + symbol_map[struct["vaddr"]] = struct["name"] + + bin_dir = f"bin/world/{area_name}/{map_name}" + src_dir = f"src/world/{area_name}/{map_name}" + + splits = [] + rom_start = 0 + with open(os.path.join(os.path.dirname(__file__), "splat.yaml")) as splat: + splat = yaml.safe_load(splat) + + for segment in splat["segments"]: + if type(segment) == dict and segment.get("name") == f"world/{area_name}/{map_name}/": + rom_start = segment.get("start", 0) + splits = segment.get("files", []) + continue + if len(splits) == 0: + print(f"unable to find {map_name} in splat.yaml") + exit(1) + + # advance to the EntryList (start of data) + while midx[0]["type"] != "EntryList": + midx.pop(0) + + for split in splits: + rom_addr = split[0] + filetype = split[1] + + if filetype == "bin": + with open(f"{bin_dir}/{rom_addr:X}.bin", "rb") as bytes: + print(f"// {rom_addr:X}") + print(disassemble(bytes, rom_addr - rom_start, midx, symbol_map, map_name)) diff --git a/tools/disasm_script.py b/tools/disasm_script.py index d9699500db..f9e249b877 100755 --- a/tools/disasm_script.py +++ b/tools/disasm_script.py @@ -42,14 +42,11 @@ def star_rod_lib(): return _star_rod_lib -def addr_ref(addr): - return star_rod_lib().get(addr, f"0x{addr:08X}") - -def disassemble(bytes, indent = 0, script_name = "script"): +def disassemble(bytes, script_name = "script", symbol_map = {}): out = "" prefix = "" - indent += 1 + indent = 1 indent_used = False def write_line(line): @@ -65,6 +62,9 @@ def disassemble(bytes, indent = 0, script_name = "script"): prefix += "\n" def var(arg): + if arg in symbol_map: + return symbol_map[arg] + v = arg - 2**32 # convert to s32 if v > -250000000: if v <= -220000000: return f"SI_FIXED({(v + 230000000) / 1024}f)" @@ -86,6 +86,11 @@ def disassemble(bytes, indent = 0, script_name = "script"): else: return f"{arg}" + def addr_ref(addr): + if addr in symbol_map: + return symbol_map[addr] + return star_rod_lib().get(addr, f"0x{addr:08X}") + def trigger(trigger): if trigger == 0x00000080: trigger = "TriggerFlag_FLOOR_TOUCH" if trigger == 0x00800000: trigger = "TriggerFlag_FLOOR_ABOVE" @@ -107,6 +112,10 @@ def disassemble(bytes, indent = 0, script_name = "script"): while True: opcode = read_word() argc = read_word() + + if opcode > 0xFF or argc > 0xFF: + return f"/* malformed script: {script_name} */\n" + argv = [] for i in range(0, argc): argv.append(read_word()) @@ -167,7 +176,7 @@ def disassemble(bytes, indent = 0, script_name = "script"): write_line(f"SI_END_IF(),") elif opcode == 0x14: write_line(f"SI_SWITCH({var(argv[0])}),") - indent += 1 + indent += 2 elif opcode == 0x15: write_line(f"SI_SWITCH_CONST(0x{argv[0]:X}),") indent += 2