mirror of
https://github.com/pmret/papermario.git
synced 2024-11-09 12:32:38 +01:00
683d0857ff
* fix ld warnings * fix NpcSettings warn * get_screen_coords * various * bloop * update INSTALL.md for versioned baseroms
715 lines
32 KiB
Python
Executable File
715 lines
32 KiB
Python
Executable File
#! /usr/bin/python3
|
|
|
|
import sys
|
|
import os
|
|
import yaml
|
|
import json
|
|
from struct import unpack, unpack_from
|
|
from copy import deepcopy
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
import disasm_script
|
|
|
|
DIR = os.path.dirname(__file__)
|
|
|
|
INCLUDED = {}
|
|
INCLUDED["functions"] = set()
|
|
INCLUDED["includes"] = set()
|
|
INCLUDES_NEEDED = {}
|
|
INCLUDES_NEEDED["include"] = []
|
|
INCLUDES_NEEDED["forward"] = []
|
|
INCLUDES_NEEDED["npcs"] = {}
|
|
INCLUDES_NEEDED["sprites"] = set()
|
|
|
|
def get_function_list(area_name, map_name, rom_offset):
|
|
map_file = (Path(__file__).parent.parent / "ver" / "current" / "build" / "papermario.map").read_text().splitlines()
|
|
i = 0
|
|
firstFind = False
|
|
functions = {}
|
|
while i < len(map_file):
|
|
if map_file[i].startswith(f" ver/us/build/src/world/area_{area_name}/{map_name}/"):
|
|
firstFind = True
|
|
i += 1
|
|
while not map_file[i].startswith(" .data"):
|
|
if map_file[i].startswith(" ") and " = ." not in map_file[i]:
|
|
line = map_file[i].strip()
|
|
vram, *_, func = line.split()
|
|
vram = int(vram, 16)
|
|
func = func.replace(f"{map_name}_", "")
|
|
if func.count("_") == 2:
|
|
func = func.rsplit("_",1)[0]
|
|
functions[vram] = func
|
|
i += 1
|
|
if firstFind:
|
|
break
|
|
i += 1
|
|
|
|
return functions
|
|
|
|
def get_include_list(area_name, map_name):
|
|
include_path = Path(__file__).parent.parent / "src" / "world" / "common"
|
|
includes = set()
|
|
for file in include_path.iterdir():
|
|
if file.is_file() and ".inc.c" in file.parts[-1]:
|
|
with open(file, "r", encoding="utf8") as f:
|
|
for line in f:
|
|
if (line.startswith("void N(") or line.startswith("ApiStatus N(")) and "{" in line:
|
|
func_name = line.split("N(",1)[1].split(")",1)[0]
|
|
includes.add(func_name)
|
|
return includes
|
|
|
|
def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0):
|
|
global INCLUDES_NEEDED, INCLUDED
|
|
out = ""
|
|
|
|
entry_list_name = None
|
|
main_script_name = None
|
|
|
|
INDENT = f" "
|
|
afterHeader = False
|
|
|
|
while len(midx) > 0:
|
|
struct = midx.pop(0)
|
|
|
|
name = struct["name"]
|
|
|
|
#INCLUDED["functions"].add(name)
|
|
|
|
if comments:
|
|
out += f"// {romstart+struct['start']:X}-{romstart+struct['end']:X} (VRAM: {struct['vaddr']:X})\n"
|
|
|
|
# format struct
|
|
if struct["type"].startswith("Script"):
|
|
if struct["type"] == "Script_Main":
|
|
name = "N(main)"
|
|
INCLUDES_NEEDED["forward"].append(f"Script " + name + ";")
|
|
main_script_name = name
|
|
|
|
# For PlayMusic script if using a separate header file
|
|
#if afterHeader:
|
|
# INCLUDES_NEEDED["forward"].append(f"Script " + name + ";")
|
|
# afterHeader = False
|
|
|
|
pos = bytes.tell()
|
|
try_replace = False
|
|
try:
|
|
script_text = disasm_script.ScriptDSLDisassembler(bytes, name, symbol_map, romstart, INCLUDES_NEEDED, INCLUDED).disassemble()
|
|
try_replace = True
|
|
except disasm_script.UnsupportedScript as e:
|
|
script_text = f"// Unable to use DSL: {e}\n"
|
|
|
|
bytes.seek(pos)
|
|
script_text = disasm_script.ScriptDisassembler(bytes, name, symbol_map, romstart, INCLUDES_NEEDED, INCLUDED).disassemble()
|
|
|
|
if try_replace and "exitWalk" in name:
|
|
script_text = script_text.splitlines()
|
|
walkDistance = exitIdx = map_ = entryIdx = ""
|
|
if "UseExitHeading" in script_text[2]:
|
|
walkDistance, exitIdx = script_text[2].split("(",1)[1].split(")",1)[0].split(",")
|
|
if "GotoMap" in script_text[4]:
|
|
map_, entryIdx = script_text[4].split("(",1)[1].split(")",1)[0].split(",")
|
|
if walkDistance and exitIdx and map_ and entryIdx:
|
|
out += f"Script {name} = EXIT_WALK_SCRIPT({walkDistance}, {exitIdx}, {map_}, {entryIdx});\n"
|
|
else:
|
|
print(f"Unable to macro replace exit script {name}")
|
|
out += "\n".join(script_text) + "\n"
|
|
else:
|
|
out += script_text
|
|
|
|
elif struct["type"] == "EntryList":
|
|
entry_list_name = name
|
|
out += f"EntryList {name} = {{"
|
|
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"] == "NpcSettings":
|
|
tmp_out = f"NpcSettings {name} = {{\n"
|
|
npcSettings = bytes.read(struct["length"])
|
|
|
|
i = 0
|
|
while i < struct["length"]:
|
|
if i == 0x0 or i == 0x24:
|
|
var_names = ["unk_00", "unk_24"]
|
|
data = unpack_from(">4B", npcSettings, i)
|
|
if not sum(data) == 0:
|
|
tmp_out += INDENT + f".{var_names[0] if i == 0 else var_names[1]} = {{ " + ", ".join(f"{x:02X}" for x in unk_00) + f" }},\n"
|
|
elif i == 0x4 or i == 0x28:
|
|
var_names = ["height", "radius", "level", "unk_2A"]
|
|
for x,var in enumerate(unpack_from(">2h", npcSettings, i)):
|
|
var_name = var_names[x if i == 0x4 else x+2]
|
|
if not var == 0:
|
|
tmp_out += INDENT + f".{var_name} = {var},\n"
|
|
elif i == 0x8:
|
|
var_names = ["otherAI", "onInteract", "ai", "onHit", "aux", "onDefeat", "flags"]
|
|
for x,var in enumerate(unpack_from(f">7I", npcSettings, i)):
|
|
var_name = var_names[x]
|
|
if not var == 0:
|
|
if var == 0x80077F70:
|
|
tmp_out += INDENT + f".{var_name} = EnemyNpcHit,\n"
|
|
elif var == 0x8007809C:
|
|
tmp_out += INDENT + f".{var_name} = EnemyNpcDefeat,\n"
|
|
elif var_name != "flags" and var in symbol_map:
|
|
tmp_out += INDENT + f".{var_name} = &{symbol_map[var][0][1]},\n"
|
|
if symbol_map[var][0][1] not in INCLUDED["functions"]:
|
|
INCLUDES_NEEDED["forward"].append(symbol_map[var][0][1])
|
|
else:
|
|
tmp_out += INDENT + f".{var_name} = 0x{var:08X},\n"
|
|
i += 1
|
|
|
|
tmp_out += "};\n"
|
|
out += tmp_out
|
|
elif struct["type"] == "AISettings":
|
|
tmp_out = f"NpcAISettings {name} = {{\n"
|
|
npcAISettings = bytes.read(struct["length"])
|
|
|
|
i = x = 0
|
|
var_names = ["moveSpeed", "moveTime", "waitTime", "alertRadius", "unk_10", "unk_14",
|
|
"chaseSpeed", "unk_1C", "unk_20", "chaseRadius", "unk_28", "unk_2C"]
|
|
while i < struct["length"]:
|
|
var_f, var_i1, var_i2 = unpack_from(f">fii", npcAISettings, i)
|
|
if not var_f == 0:
|
|
tmp_out += INDENT + f".{var_names[x]} = {var_f:.01f}f,\n"
|
|
if not var_i1 == 0:
|
|
# account for X32
|
|
if var_names[x + 1] in ["unk_10", "unk_1C", "unk_28"]:
|
|
if var_i1 < -100000 or var_i1 > 100000:
|
|
tmp_out += INDENT + f".{var_names[x + 1]} = {{ .f = {unpack_from('>f', npcAISettings, i+4)[0]:.01f}f }},\n"
|
|
else:
|
|
tmp_out += INDENT + f".{var_names[x + 1]} = {{ .s = {var_i1} }},\n"
|
|
else:
|
|
tmp_out += INDENT + f".{var_names[x + 1]} = {var_i1},\n"
|
|
if not var_i2 == 0:
|
|
tmp_out += INDENT + f".{var_names[x + 2]} = {var_i2},\n"
|
|
i += 12
|
|
x += 3
|
|
|
|
tmp_out += "};\n"
|
|
out += tmp_out
|
|
elif struct["type"] == "NpcGroup":
|
|
staticNpc = bytes.read(struct["length"])
|
|
curr_base = 0
|
|
numNpcs = struct['length'] // 0x1F0
|
|
tmp_out = f"StaticNpc {name}" + ("[]" if numNpcs > 1 else "") + f" = {{\n"
|
|
|
|
for z in range(numNpcs):
|
|
i = 0
|
|
var_names = ["id", "settings", "pos", "flags",
|
|
"init", "unk_1C", "yaw", "dropFlags",
|
|
"itemDropChance", "itemDrops", "heartDrops", "flowerDrops",
|
|
"minCoinBonus", "maxCoinBonus", "movement", "animations",
|
|
"unk_1E0", "extraAnimations", "tattle"]
|
|
|
|
if numNpcs > 1:
|
|
tmp_out += INDENT + f"{{\n"
|
|
INDENT = INDENT*2
|
|
|
|
while i < 0x1F0:
|
|
if i == 0x0 or i == 0x24:
|
|
var_name = var_names[0] if i == 0x0 else var_names[6]
|
|
var = unpack_from(f">i", staticNpc, curr_base+i)[0]
|
|
if var_name == "id":
|
|
tmp_out += INDENT + f".{var_name} = {disasm_script.CONSTANTS['MAP_NPCS'][var]},\n"
|
|
else:
|
|
tmp_out += INDENT + f".{var_name} = {var},\n"
|
|
elif i == 0x4 or i == 0x14 or i == 0x18 or i == 0x1E8:
|
|
var_name = var_names[1] if i == 0x4 else var_names[3] if i == 0x14 else var_names[4] if i == 0x18 else var_names[17]
|
|
addr = unpack_from(f">I", staticNpc, curr_base+i)[0]
|
|
if not addr == 0:
|
|
if var_name != "flags" and addr in symbol_map:
|
|
tmp_out += INDENT + f".{var_name} = &{symbol_map[addr][0][1]},\n"
|
|
if symbol_map[addr][0][1] not in INCLUDED["functions"]:
|
|
INCLUDES_NEEDED["forward"].append(symbol_map[addr][0][1])
|
|
else:
|
|
enabled = []
|
|
for x in range(32):
|
|
val = addr & (1 << x)
|
|
if val:
|
|
if val in disasm_script.CONSTANTS["NpcFlags"]:
|
|
enabled.append(disasm_script.CONSTANTS["NpcFlags"][val])
|
|
else:
|
|
print(f"NpcFlag 0x{val:08X} missing from NpcFlag enums!")
|
|
enabled.append(f"0x{val:08X}")
|
|
if not enabled:
|
|
enabled.append(0)
|
|
tmp_out += INDENT + f".{var_name} = " + " | ".join(enabled) + f",\n"
|
|
elif i == 0x8:
|
|
pos = unpack_from(f">fff", staticNpc, curr_base+i)
|
|
if not sum(pos) == 0:
|
|
tmp_out += INDENT + f".pos = {{ {pos[0]:.01f}f, {pos[1]:.01f}f, {pos[2]:.01f}f }},\n"
|
|
elif i == 0x1C or i == 0x1E0:
|
|
var_name = var_names[5] if i == 0x1C else var_names[16]
|
|
data = unpack_from(f">8B", staticNpc, curr_base+i)
|
|
if not sum(data) == 0:
|
|
tmp_out += INDENT + f".{var_name} = {{ " + ", ".join(f"{x:02X}" for x in data) + f"}},\n"
|
|
elif i == 0x28 or i == 0x29:
|
|
var_name = var_names[7] if i == 0x28 else var_names[8]
|
|
var = unpack_from(f">b", staticNpc, curr_base+i)[0]
|
|
if not var == 0:
|
|
if var_name == "dropFlags":
|
|
tmp_out += INDENT + f".{var_name} = 0x{abs(var):02X},\n"
|
|
else:
|
|
tmp_out += INDENT + f".{var_name} = {var},\n"
|
|
elif i == 0x2A:
|
|
var_name = var_names[9]
|
|
tmp_tmp = ""
|
|
for x in range(8):
|
|
item, weight, unk_08 = unpack_from(f">3h", staticNpc, curr_base+i)
|
|
if not (item == 0 and weight == 0 and unk_08 == 0):
|
|
item = disasm_script.CONSTANTS["ItemIDs"][item] if item in disasm_script.CONSTANTS["ItemIDs"] else f"{item}"
|
|
tmp_tmp += INDENT*2 + f"{{ {item}, {weight}, {unk_08} }},\n"
|
|
i += 0x6
|
|
|
|
if tmp_tmp:
|
|
tmp_out += INDENT + f".{var_name} = {{\n"
|
|
tmp_out += tmp_tmp
|
|
tmp_out += INDENT + f"}},\n"
|
|
|
|
i -= 1
|
|
elif i == 0x5A or i == 0x9A:
|
|
var_name = var_names[10] if i == 0x5A else var_names[11]
|
|
drops = []
|
|
for x in range(8):
|
|
cutoff, generalChance, attempts, chancePerAttempt = unpack_from(f">4h", staticNpc, curr_base+i)
|
|
if not (cutoff == 0 and generalChance == 0 and attempts == 0 and chancePerAttempt == 0):
|
|
drops.append([cutoff, generalChance, attempts, chancePerAttempt])
|
|
i += 0x8
|
|
i -= 1
|
|
if drops:
|
|
tmp_out += INDENT + f".{var_name} = "
|
|
if var_name == "heartDrops":
|
|
if round(drops[0][1] / 327.67, 2) == 70 and round(drops[0][3] / 327.67, 2) == 50:
|
|
tmp_out += f"STANDARD_HEART_DROPS({drops[0][2]})"
|
|
elif round(drops[0][1] / 327.67, 2) == 80 and round(drops[0][3] / 327.67, 2) == 50:
|
|
tmp_out += f"GENEROUS_HEART_DROPS({drops[0][2]})"
|
|
elif round(drops[0][1] / 327.67, 2) == 80 and round(drops[0][3] / 327.67, 2) == 60:
|
|
tmp_out += f"GENEROUS_WHEN_LOW_HEART_DROPS({drops[0][2]})"
|
|
elif round(drops[0][0] / 327.67, 2) == 100 and round(drops[0][1] / 327.67, 2) == 0 and round(drops[0][2] / 327.67, 2) == 0:
|
|
tmp_out += f"NO_DROPS"
|
|
else:
|
|
print(f"Unknown heart drop macro, values were {round(drops[0][1] / 327.67, 2)} and {round(drops[0][3] / 327.67, 2)}")
|
|
exit()
|
|
else:
|
|
if round(drops[0][1] / 327.67, 2) == 50 and round(drops[0][3] / 327.67, 2) == 40:
|
|
tmp_out += f"STANDARD_FLOWER_DROPS({drops[0][2]})"
|
|
elif round(drops[0][1] / 327.67, 2) == 70 and round(drops[0][3] / 327.67, 2) == 50:
|
|
tmp_out += f"GENEROUS_WHEN_LOW_FLOWER_DROPS({drops[0][2]})"
|
|
elif round(drops[0][1] / 327.67, 2) == 40 and round(drops[0][3] / 327.67, 2) == 40:
|
|
tmp_out += f"REDUCED_FLOWER_DROPS({drops[0][2]})"
|
|
elif round(drops[0][0] / 327.67, 2) == 100 and round(drops[0][1] / 327.67, 2) == 0 and round(drops[0][2] / 327.67, 2) == 0:
|
|
tmp_out += f"NO_DROPS"
|
|
else:
|
|
print(f"Unknown flower drop macro, values were {round(drops[0][1] / 327.67, 2)} and {round(drops[0][3] / 327.67, 2)}")
|
|
exit()
|
|
|
|
tmp_out += f",\n"
|
|
elif i == 0xDA or i == 0xDC:
|
|
var_name = var_names[12] if i == 0xDA else var_names[13]
|
|
var = unpack_from(">h", staticNpc, curr_base+i)[0]
|
|
if not var == 0:
|
|
tmp_out += INDENT + f".{var_name} = {var},\n"
|
|
elif i == 0xE0:
|
|
data = unpack_from(">48i", staticNpc, curr_base+i)
|
|
if not sum(data) == 0:
|
|
end_pos = len(data)
|
|
for x,datum in enumerate(data):
|
|
if not datum == 0:
|
|
end_pos = x
|
|
tmp_out += INDENT + f".movement = {{ " + ", ".join(f"{x}" for x in data[:end_pos+1]) + f" }},\n"
|
|
elif i == 0x1A0:
|
|
tmp_out += INDENT + f".{var_names[15]} = {{\n"
|
|
for x in range(16):
|
|
anim = unpack_from(">I", staticNpc, curr_base+i)[0]
|
|
if not anim == 0:
|
|
sprite_id = (anim & 0x00FF0000) >> 16
|
|
palette_id = (anim & 0x0000FF00) >> 8
|
|
anim_id = (anim & 0x000000FF) >> 0
|
|
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
|
|
palette = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["palettes"][palette_id]
|
|
anim = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
|
|
if numNpcs > 1:
|
|
tmp_out += INDENT + " " + f"NPC_ANIM({sprite}, {palette}, {anim}),\n"
|
|
else:
|
|
tmp_out += INDENT*2 + f"NPC_ANIM({sprite}, {palette}, {anim}),\n"
|
|
INCLUDES_NEEDED["sprites"].add(sprite)
|
|
i += 4
|
|
tmp_out += INDENT + f"}},\n"
|
|
i -= 1
|
|
elif i == 0x1EC:
|
|
var = unpack_from(">I", staticNpc, curr_base+i)[0]
|
|
if not var == 0:
|
|
tmp_out += INDENT + f".{var_names[18]} = MESSAGE_ID(0x{(var & 0xFF0000) >> 16:02X}, 0x{var & 0xFFFF:04X}),\n"
|
|
|
|
i += 1
|
|
|
|
if numNpcs > 1:
|
|
INDENT = INDENT[:len(INDENT)//2]
|
|
tmp_out += INDENT + f"}},\n"
|
|
if z+1 == numNpcs:
|
|
tmp_out += "};\n"
|
|
|
|
curr_base += 0x1F0
|
|
out += tmp_out
|
|
elif struct["type"] == "ExtraAnimationList":
|
|
tmp_out = f"NpcAnimID {name}[] = {{\n"
|
|
extraAnimations = bytes.read(struct["length"])
|
|
|
|
i = 0
|
|
while i < struct["length"]:
|
|
anim = unpack_from(">I", extraAnimations, i)[0]
|
|
if anim == 0xFFFFFFFF:
|
|
tmp_out += INDENT + f"ANIM_END,\n"
|
|
elif not anim == 0:
|
|
sprite_id = (anim & 0x00FF0000) >> 16
|
|
palette_id = (anim & 0x0000FF00) >> 8
|
|
anim_id = (anim & 0x000000FF) >> 0
|
|
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"]
|
|
palette = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["palettes"][palette_id]
|
|
anim = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["anims"][anim_id]
|
|
tmp_out += INDENT + f"NPC_ANIM({sprite}, {palette}, {anim}),\n"
|
|
INCLUDES_NEEDED["sprites"].add(sprite)
|
|
i += 4
|
|
tmp_out += f"}};\n"
|
|
out += tmp_out
|
|
elif struct["type"] == "NpcGroupList":
|
|
tmp_out = f"NpcGroupList {name} = {{\n"
|
|
npcGroupList = bytes.read(struct["length"])
|
|
|
|
i = 0
|
|
while i < struct["length"]:
|
|
npcCount, npcs, battle = unpack_from(">3I", npcGroupList, i)
|
|
if not (npcCount == 0 and npcs == 0 and battle == 0):
|
|
battle_a = (battle & 0xFF000000) >> 24
|
|
battle_b = (battle & 0x00FF0000) >> 16
|
|
battle_c = (battle & 0x0000FF00) >> 8
|
|
battle_d = (battle & 0x000000FF) >> 0
|
|
tmp_out += INDENT + f"NPC_GROUP({symbol_map[npcs][0][1]}, BATTLE_ID({battle_a}, {battle_b}, {battle_c}, {battle_d})),\n"
|
|
if symbol_map[npcs][0][1] not in INCLUDED["functions"]:
|
|
INCLUDES_NEEDED["forward"].append(symbol_map[npcs][0][1])
|
|
i += 0xC
|
|
tmp_out += INDENT + f"{{}},\n"
|
|
tmp_out += f"}};\n"
|
|
out += tmp_out
|
|
elif struct["type"] == "ItemList":
|
|
out += f"s32 {name}[] = {{\n"
|
|
|
|
items = unpack(f">{struct['length']//4}I", bytes.read(struct["length"]))
|
|
for item in items:
|
|
out += f" {disasm_script.CONSTANTS['ItemIDs'][item]},\n"
|
|
out += f"}};\n"
|
|
|
|
elif struct["type"] == "Header":
|
|
out += f"MapConfig N(config) = {{\n"
|
|
|
|
bytes.read(0x10)
|
|
|
|
main,entry_list,entry_count = unpack(">IIi", bytes.read(4 * 3))
|
|
out += f" .main = &N(main),\n"
|
|
out += f" .entryList = &{entry_list_name},\n"
|
|
out += f" .entryCount = ENTRY_COUNT({entry_list_name}),\n"
|
|
|
|
bytes.read(0x1C)
|
|
|
|
bg,tattle = unpack(">II", bytes.read(4 * 2))
|
|
if bg == 0x80200000:
|
|
out += f" .background = &gBackgroundImage,\n"
|
|
elif bg != 0:
|
|
raise Exception(f"unknown MapConfig background {bg:X}")
|
|
out += f" .tattle = {{ 0x{tattle:X} }},\n"
|
|
|
|
out += f"}};\n"
|
|
afterHeader = True
|
|
elif struct["type"] == "ASCII" or struct["type"] == "SJIS":
|
|
# rodata string hopefully inlined elsewhere
|
|
bytes.read(struct["length"])
|
|
out += f"// rodata: {struct['name']}\n"
|
|
elif struct["type"].startswith("Function"):
|
|
bytes.read(struct["length"])
|
|
out += f"s32 {name}();\n"
|
|
elif struct["type"] == "FloatTable":
|
|
vram = int(name.split("_",1)[1][:-1], 16)
|
|
name = f"N(D_{vram:X}_{(vram - 0x80240000) + romstart:X})"
|
|
struct["name"] = name
|
|
out += f"f32 {name}[] = {{"
|
|
for i in range(0, struct["length"], 4):
|
|
if (i % (4 * 4)) == 0:
|
|
out += f"\n "
|
|
|
|
word = unpack(">f", bytes.read(4))[0]
|
|
out += f" {word:.01f}f,"
|
|
|
|
out += f"\n}};\n"
|
|
elif struct["type"] == "Formation":
|
|
out += f"Formation {struct['name']} = {{\n"
|
|
|
|
num_bytes_remaining = struct["length"]
|
|
while num_bytes_remaining > 0:
|
|
num_read, s = disasm_struct.output_single_line(bytes.read(0x1C), 0, "FormationRow")
|
|
num_bytes_remaining -= num_read
|
|
|
|
s = s.replace(", .var0 = 0, .var1 = 0, .var2 = 0, .var3 = 0", "")
|
|
|
|
out += f" {s},\n"
|
|
|
|
out += f"}};\n"
|
|
else: # unknown type of struct
|
|
if struct["name"].startswith("N(unk_802"):
|
|
vram = int(name.split("_",1)[1][:-1], 16)
|
|
name = f"N(D_{vram:X}_{(vram - 0x80240000) + romstart:X})"
|
|
struct["name"] = name
|
|
|
|
if struct["type"] == "Padding":
|
|
out += "static "
|
|
if struct["length"] // 4 > 1:
|
|
out += f"s32 {name}[] = {{"
|
|
else:
|
|
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][0][1]},"
|
|
else:
|
|
out += f" 0x{word:08X},"
|
|
|
|
out += f"\n}};\n"
|
|
|
|
out += "\n"
|
|
if not struct["type"] == "Function":
|
|
INCLUDED["functions"].add(name)
|
|
|
|
# end of data
|
|
return out
|
|
|
|
def parse_midx(file, prefix="", vram=0x80240000):
|
|
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": "N(" + 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 + vram
|
|
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 + vram
|
|
structs.append({
|
|
"name": f"{prefix}pad_{start:X}",
|
|
"type": "Padding",
|
|
"start": start,
|
|
"vaddr": vaddr,
|
|
"length": end - start,
|
|
"end": end,
|
|
})
|
|
|
|
structs.sort(key=lambda s: s["start"])
|
|
return structs
|
|
|
|
def name_struct(s):
|
|
s = s[1:].replace("???", "unk")
|
|
|
|
s = s.replace("Function", "func")
|
|
|
|
"""
|
|
# use ThisCase for scripts
|
|
if s.startswith("Script_"):
|
|
s = s[7].upper() + s[8:]
|
|
|
|
# if `s` is hex, prefix it with Script_ again
|
|
try:
|
|
int(s, 16)
|
|
return "Script_" + s
|
|
except Exception:
|
|
pass
|
|
|
|
if s.startswith("Main"):
|
|
return "Main"
|
|
|
|
return s
|
|
"""
|
|
|
|
if s.startswith("ASCII"):
|
|
return s
|
|
|
|
return s[0].lower() + s[1:]
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Converts split data to C using a Star Rod idx file")
|
|
parser.add_argument("idxfile", help="Input .*idx file from Star Rod dump")
|
|
parser.add_argument("--comments", action="store_true", help="Write offset/vaddr comments")
|
|
|
|
args = parser.parse_args()
|
|
|
|
base, ext = os.path.splitext(os.path.basename(args.idxfile))
|
|
if ext == ".midx":
|
|
map_name = base
|
|
area_name, _ = map_name.split("_")
|
|
segment_name = f"world/area_{area_name}/{map_name}/"
|
|
else:
|
|
battle_area = "_".join(base.lower().split(" ")[1:])
|
|
segment_name = f"battle/{battle_area}/"
|
|
|
|
symbol_map = {}
|
|
|
|
disasm_script.get_constants()
|
|
|
|
with open(os.path.join(DIR, "../ver/current/splat.yaml")) as f:
|
|
splat_config = yaml.safe_load(f.read())
|
|
|
|
rom_offset = -1
|
|
for segment in splat_config["segments"]:
|
|
if isinstance(segment, dict) and segment.get("name") == segment_name:
|
|
rom_offset = segment["start"]
|
|
vram = segment["vram"]
|
|
break
|
|
|
|
if rom_offset == -1:
|
|
print(f"can't find segment with name '{segment_name}' in splat.yaml")
|
|
exit(1)
|
|
|
|
function_replacements = get_function_list(area_name, map_name, rom_offset)
|
|
INCLUDED["includes"] = get_include_list(area_name, map_name)
|
|
|
|
with open(args.idxfile, "r") as f:
|
|
midx = parse_midx(f, vram=vram)
|
|
|
|
with open(os.path.join(DIR, "../ver/current/baserom.z64"), "rb") as romfile:
|
|
name_fixes = {
|
|
"script_NpcAI": "npcAI",
|
|
"aISettings": "npcAISettings",
|
|
"script_ExitWalk": "exitWalk",
|
|
"script_MakeEntities": "makeEntities",
|
|
}
|
|
total_npc_counts = {}
|
|
for struct in midx:
|
|
romfile.seek(struct["start"] + rom_offset)
|
|
|
|
name = struct["name"]
|
|
|
|
if name.startswith("N("):
|
|
name = name[2:-1]
|
|
|
|
if struct['vaddr'] in function_replacements:
|
|
name = function_replacements[struct['vaddr']]
|
|
|
|
if name.split("_",1)[0] in name_fixes:
|
|
name = name_fixes[name.split("_",1)[0]] + "_" + name.rsplit("_",1)[1]
|
|
elif name.startswith("script_"):
|
|
name = name.split("script_",1)[1]
|
|
elif "_Main_" in name:
|
|
name = "main"
|
|
elif "ASCII" in name:
|
|
name = name.replace("ASCII", "ascii")
|
|
|
|
if name not in INCLUDED["includes"]:
|
|
name = name[0].lower() + name[1:]
|
|
|
|
name = "N(" + name + ")"
|
|
struct["name"] = name
|
|
|
|
# decode rodata stuff so it can be written inline instead of by pointer (which wouldn't match)
|
|
if struct["type"] == "ASCII":
|
|
string_data = romfile.read(struct["length"]).decode("ascii")
|
|
|
|
# strip null terminator(s)
|
|
while string_data[-1] == "\0":
|
|
string_data = string_data[:-1]
|
|
|
|
string_literal = json.dumps(string_data)
|
|
symbol_map[struct["vaddr"]] = [[struct["vaddr"], string_literal]]
|
|
elif struct["type"] == "SJIS":
|
|
string_data = sjis.decode(romfile.read(struct["length"]))
|
|
|
|
string_literal = '"' + string_data + '"'
|
|
symbol_map[struct["vaddr"]] = [[struct["vaddr"], string_literal]]
|
|
elif struct["type"] == "NpcGroup":
|
|
for z in range(struct["length"]//0x1F0):
|
|
npc = romfile.read(0x1F0)
|
|
npc_id = unpack_from(">I", npc, 0)[0]
|
|
if npc_id >= 0:
|
|
anim = unpack_from(">I", npc, 0x1A0)[0]
|
|
if not anim == 0:
|
|
sprite_id = (anim & 0x00FF0000) >> 16
|
|
sprite = disasm_script.CONSTANTS["NPC_SPRITE"][sprite_id]["name"].upper()
|
|
if npc_id not in total_npc_counts:
|
|
total_npc_counts[npc_id] = sprite
|
|
symbol_map[struct["vaddr"]] = [[struct["vaddr"], struct["name"]]]
|
|
else:
|
|
symbol_map[struct["vaddr"]] = [[struct["vaddr"], struct["name"]]]
|
|
|
|
# fix NPC names
|
|
curr_counts = {}
|
|
for id_, name in total_npc_counts.items():
|
|
if sum(x == name for x in total_npc_counts.values()) > 1:
|
|
if name not in curr_counts:
|
|
curr_counts[name] = 0
|
|
nname = name
|
|
name = name + f"{curr_counts[name]}"
|
|
curr_counts[nname] += 1
|
|
name = f"NPC_{name}"
|
|
disasm_script.CONSTANTS["MAP_NPCS"][id_] = name
|
|
INCLUDES_NEEDED["npcs"][id_] = name
|
|
|
|
for id_, name in disasm_script.CONSTANTS["NpcIDs"].items():
|
|
disasm_script.CONSTANTS["MAP_NPCS"][id_] = name
|
|
|
|
romfile.seek(rom_offset, 0)
|
|
|
|
disasm = disassemble(romfile, midx, symbol_map, args.comments, rom_offset)
|
|
|
|
print("========== Includes needed: ===========\n")
|
|
print(f"#include \"map.h\"")
|
|
print(f"#include \"message_ids.h\"")
|
|
if INCLUDES_NEEDED["sprites"]:
|
|
for npc in sorted(INCLUDES_NEEDED["sprites"]):
|
|
print(f"#include \"sprite/npc/{npc}.h\"")
|
|
print()
|
|
|
|
if INCLUDES_NEEDED["forward"]:
|
|
print()
|
|
print("========== Forward declares: ==========\n")
|
|
for forward in sorted(INCLUDES_NEEDED["forward"]):
|
|
print(forward)
|
|
print()
|
|
|
|
if INCLUDES_NEEDED["npcs"]:
|
|
print("========== NPCs needed: ===========\n")
|
|
print(f"enum {{")
|
|
lastnum = -1
|
|
for i, (k, v) in enumerate(sorted(INCLUDES_NEEDED["npcs"].items())):
|
|
print(f" {v}" + (f" = {k}" if ((k > 0 and i == 0) or (k != lastnum+1)) else "") + ",")
|
|
lastnum = k
|
|
print(f"}};")
|
|
print()
|
|
|
|
print("=======================================\n")
|
|
print(disasm.rstrip())
|
|
|
|
|