mirror of
https://github.com/pmret/papermario.git
synced 2024-11-18 00:42:34 +01:00
ae66312d8c
* Add Python linter to github actions * wip * Add back splat_ext * Format files * C++ -> C * format 2 files * split workflow into separate file, line length 120, fix excludes * -l 120 in ci * update black locally and apply formatting changes * pyproject.toject --------- Co-authored-by: Ethan Roseman <ethteck@gmail.com>
1367 lines
54 KiB
Python
Executable File
1367 lines
54 KiB
Python
Executable File
#!/usr/bin/env 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
|
|
import sjis
|
|
|
|
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()
|
|
INCLUDES_NEEDED["tattle"] = []
|
|
|
|
|
|
def get_flag_name(arg):
|
|
v = arg - 2**32 # convert to s32
|
|
if v > -250000000:
|
|
if v <= -220000000:
|
|
return str((v + 230000000) / 1024)
|
|
elif v <= -200000000:
|
|
return f"ArrayFlag({v + 210000000})"
|
|
elif v <= -180000000:
|
|
return f"ArrayVar({v + 190000000})"
|
|
elif v <= -160000000:
|
|
if v + 170000000 == 0:
|
|
return "GB_StoryProgress"
|
|
elif v + 170000000 == 425:
|
|
return "GB_WorldLocation"
|
|
else:
|
|
return f"GameByte({v + 170000000})"
|
|
elif v <= -140000000:
|
|
return f"AreaByte({v + 150000000})"
|
|
elif v <= -120000000:
|
|
return f"GameFlag({v + 130000000})"
|
|
elif v <= -100000000:
|
|
return f"AreaFlag({v + 110000000})"
|
|
elif v <= -80000000:
|
|
return f"MapFlag({v + 90000000})"
|
|
elif v <= -60000000:
|
|
return f"LocalFlag({v + 70000000})"
|
|
elif v <= -40000000:
|
|
return f"MapVar({v + 50000000})"
|
|
elif v <= -20000000:
|
|
return f"LocalVar({v + 30000000})"
|
|
|
|
if arg == 0xFFFFFFFF:
|
|
return "-1"
|
|
elif (arg & 0xFF000000) == 0x80000000:
|
|
return f"0x{arg:X}"
|
|
elif arg >= 0x80000000:
|
|
return f"{arg - 0x100000000}"
|
|
else:
|
|
return f"{arg}"
|
|
|
|
|
|
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".{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 read_enum(num: int, constants_name: str) -> str:
|
|
if num in disasm_script.CONSTANTS[constants_name]:
|
|
return disasm_script.CONSTANTS[constants_name][num]
|
|
else:
|
|
return num
|
|
|
|
|
|
def read_flags(flags: int, constants_name: str) -> str:
|
|
enabled = []
|
|
for x in range(32):
|
|
val = flags & (1 << x)
|
|
if val:
|
|
if val in disasm_script.CONSTANTS[constants_name]:
|
|
enabled.append(disasm_script.CONSTANTS[constants_name][val])
|
|
else:
|
|
print(f"0x{val:08X} missing from enum {constants_name}!")
|
|
enabled.append(f"0x{val:08X}")
|
|
if not enabled:
|
|
if 0 in disasm_script.CONSTANTS[constants_name]:
|
|
enabled.append(disasm_script.CONSTANTS[constants_name][0])
|
|
else:
|
|
enabled.append("0")
|
|
|
|
return " | ".join(enabled)
|
|
|
|
|
|
def read_ptr(addr: int, symbol_map: dict, needs_ampersand: bool = False) -> str:
|
|
if addr == 0:
|
|
return "NULL"
|
|
elif addr in symbol_map:
|
|
if needs_ampersand:
|
|
return f"&{symbol_map[addr][0][1]}"
|
|
else:
|
|
return f"{symbol_map[addr][0][1]}"
|
|
else:
|
|
return f"(void*) 0x{addr:08X}"
|
|
|
|
|
|
def disassemble(bytes, midx, symbol_map={}, comments=True, romstart=0, namespace=None):
|
|
global INCLUDES_NEEDED, INCLUDED
|
|
out = ""
|
|
|
|
entry_list_name = None
|
|
main_script_name = None
|
|
|
|
INDENT = f" "
|
|
afterHeader = False
|
|
treePrint = False
|
|
|
|
def transform_symbol_name(symbol):
|
|
if namespace and symbol.startswith(namespace + "_"):
|
|
return "N(" + symbol[len(namespace) + 1 :] + ")"
|
|
return symbol
|
|
|
|
while len(midx) > 0:
|
|
struct = midx.pop(0)
|
|
|
|
name = struct["name"]
|
|
print(name, file=sys.stderr)
|
|
|
|
# INCLUDED["functions"].add(name)
|
|
|
|
if comments:
|
|
out += f"// {romstart+struct['start']:X}-{romstart+struct['end']:X} (VRAM: {struct['vaddr']:X})\n"
|
|
|
|
if struct["type"] == "ASCII" or struct["type"] == "SJIS" or struct["type"] == "ConstDouble":
|
|
# rodata string hopefully inlined elsewhere
|
|
out += f"// rodata: {struct['name']}\n"
|
|
|
|
# format struct
|
|
if struct["type"].startswith("Script"):
|
|
if struct["type"] == "Script_Main":
|
|
name = "N(main)"
|
|
INCLUDES_NEEDED["forward"].append(f"EvtScript " + name + ";")
|
|
main_script_name = name
|
|
|
|
# For PlayMusic script if using a separate header file
|
|
# if afterHeader:
|
|
# INCLUDES_NEEDED["forward"].append(f"EvtScript " + name + ";")
|
|
# afterHeader = False
|
|
disasm_script.LOCAL_WORDS = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
script_text = disasm_script.ScriptDisassembler(
|
|
bytes,
|
|
name,
|
|
symbol_map,
|
|
romstart,
|
|
INCLUDES_NEEDED,
|
|
INCLUDED,
|
|
transform_symbol_name=transform_symbol_name,
|
|
use_script_lib=False,
|
|
).disassemble()
|
|
|
|
if "EVS_ShakeTree" in name or "EVS_SearchBush" in name:
|
|
symbol_map[struct["vaddr"]][0][1] = name.split("_", 1)[0] + ")"
|
|
if not treePrint:
|
|
out += f"=======================================\n"
|
|
out += f"==========BELOW foliage.inc.c==========\n"
|
|
out += f"=======================================\n\n"
|
|
treePrint = True
|
|
continue
|
|
|
|
if "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"EvtScript {name} = EVT_EXIT_WALK({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} = {{"
|
|
entry_list = bytes.read(struct["length"])
|
|
entry_count = len(entry_list) // 16
|
|
pos = 0
|
|
x = []
|
|
y = []
|
|
z = []
|
|
w = []
|
|
for _ in range(entry_count):
|
|
a, b, c, d = unpack_from(">ffff", entry_list, pos)
|
|
x.append(f"{a:.01f}")
|
|
y.append(f"{b:.01f}")
|
|
z.append(f"{c:.01f}")
|
|
w.append(f"{d:.01f}")
|
|
pos += 16
|
|
|
|
x_size = max([len(a) for a in x])
|
|
y_size = max([len(a) for a in y])
|
|
z_size = max([len(a) for a in z])
|
|
w_size = max([len(a) for a in w])
|
|
|
|
for a, b, c, d in zip(x, y, z, w):
|
|
out += f"\n {{ {a:>{x_size}}f, {b:>{y_size}}f, {c:>{z_size}}f, {d:>{w_size}}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"0x{x:02X}" for x in data)
|
|
+ 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"MobileAISettings {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"NpcData {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:
|
|
if var_name == "extraAnimations":
|
|
tmp_out += INDENT + f".{var_name} = {symbol_map[addr][0][1]},\n"
|
|
else:
|
|
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".territory = {{ .temp = {{ "
|
|
+ ", ".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"s32 {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_LIST_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"] == "TreeDropList":
|
|
new_name = "N(" + name.split("_", 1)[1][:-1].lower() + "_Drops)"
|
|
symbol_map[struct["vaddr"]][0][1] = new_name
|
|
|
|
out += f"FoliageDropList {new_name} = {{\n"
|
|
|
|
data = bytes.read(struct["length"])
|
|
count = unpack_from(">I", data, 0)[0]
|
|
|
|
out += f"{INDENT}.count = {count},\n"
|
|
|
|
if count > 0:
|
|
out += f"{INDENT}.drops = {{\n"
|
|
|
|
pos = 4
|
|
for _ in range(count):
|
|
entry = list(unpack_from(">7I", data, pos))
|
|
pos += 7 * 4
|
|
|
|
entry[1] = entry[1] - 0x100000000 if entry[1] >= 0x80000000 else entry[1]
|
|
entry[2] = entry[2] - 0x100000000 if entry[2] >= 0x80000000 else entry[2]
|
|
entry[3] = entry[3] - 0x100000000 if entry[3] >= 0x80000000 else entry[3]
|
|
|
|
flag1 = get_flag_name(entry[5])
|
|
flag2 = get_flag_name(entry[6])
|
|
|
|
out += f"{INDENT * 2}{{\n"
|
|
out += f"{INDENT * 3}.itemID = {disasm_script.CONSTANTS['ItemIDs'][entry[0]]},\n"
|
|
out += f"{INDENT * 3}.pos = {{ {entry[1]}, {entry[2]}, {entry[3]} }},\n"
|
|
if entry[4] != 0:
|
|
out += f"{INDENT * 3}.spawnMode = 0x{entry[4]:X},\n"
|
|
if flag1 != "0":
|
|
out += f"{INDENT * 3}.pickupFlag = {flag1},\n"
|
|
if flag2 != "0":
|
|
out += f"{INDENT * 3}.spawnFlag = {flag2},\n"
|
|
out += f"{INDENT * 2}}},\n"
|
|
|
|
if count > 0:
|
|
out += f"{INDENT}}}\n"
|
|
|
|
out += f"}};\n"
|
|
|
|
elif struct["type"] == "TreeModelList" or struct["type"] == "TreeEffectVectors":
|
|
isModelList = struct["type"] == "TreeModelList"
|
|
|
|
name_parts = name.split("_")
|
|
if isModelList:
|
|
new_name = "N(" + name_parts[1].lower() + "_" + name_parts[2]
|
|
else:
|
|
new_name = "N(" + name_parts[1][:-1].lower() + "_Vectors)"
|
|
symbol_map[struct["vaddr"]][0][1] = new_name
|
|
|
|
if isModelList:
|
|
out += f"FoliageModelList {new_name} = {{\n"
|
|
else:
|
|
out += f"TreeEffectVectors {new_name} = {{\n"
|
|
|
|
data = bytes.read(struct["length"])
|
|
count = unpack_from(">I", data, 0)[0]
|
|
|
|
out += f"{INDENT}.count = {count},\n"
|
|
|
|
if isModelList:
|
|
if count > 0:
|
|
out += f"{INDENT}.models = {{ "
|
|
|
|
pos = 4
|
|
for _ in range(count):
|
|
entry = unpack_from(">I", data, pos)[0]
|
|
pos += 4
|
|
|
|
out += f"{entry}, "
|
|
|
|
if count > 0:
|
|
out = out[:-2]
|
|
out += f" }}\n"
|
|
|
|
else:
|
|
if count > 0:
|
|
out += f"{INDENT}.vectors = {{\n"
|
|
|
|
pos = 4
|
|
for _ in range(count):
|
|
entry = list(unpack_from(">3I", data, pos))
|
|
|
|
entry[0] = entry[0] - 0x100000000 if entry[0] >= 0x80000000 else entry[0]
|
|
entry[1] = entry[1] - 0x100000000 if entry[1] >= 0x80000000 else entry[1]
|
|
entry[2] = entry[2] - 0x100000000 if entry[2] >= 0x80000000 else entry[2]
|
|
|
|
pos += 3 * 4
|
|
|
|
out += f"{INDENT * 2}{{ {entry[0]}, {entry[1]}, {entry[2]} }},\n"
|
|
|
|
if count > 0:
|
|
out += f"{INDENT}}}\n"
|
|
|
|
out += f"}};\n"
|
|
|
|
elif struct["type"] == "SearchBushEvent":
|
|
new_name = "N(" + name.split("_", 1)[1].lower()
|
|
symbol_map[struct["vaddr"]][0][1] = new_name
|
|
|
|
num = int(new_name.split("bush", 1)[1][:-1])
|
|
out += f"SearchBushConfig {new_name} = {{\n"
|
|
|
|
data = bytes.read(struct["length"])
|
|
entry = unpack_from(">4I", data, 0)
|
|
|
|
if entry[0] != 0:
|
|
out += f"{INDENT}.bush = &N(bush{num}_Bush),\n"
|
|
if entry[1] != 0:
|
|
out += f"{INDENT}.drops = &N(bush{num}_Drops),\n"
|
|
if entry[2] != 0:
|
|
out += f"{INDENT}.vectors = &N(bush{num}_Vectors),\n"
|
|
if entry[3] != 0:
|
|
out += f"{INDENT}.callback = &N(bush{num}_Callback),\n"
|
|
|
|
out += f"}};\n"
|
|
|
|
elif struct["type"] == "ShakeTreeEvent":
|
|
new_name = "N(" + name.split("_", 1)[1].lower()
|
|
symbol_map[struct["vaddr"]][0][1] = new_name
|
|
|
|
num = int(new_name.split("tree", 1)[1][:-1])
|
|
out += f"ShakeTreeConfig {new_name} = {{\n"
|
|
|
|
data = bytes.read(struct["length"])
|
|
entry = unpack_from(">5I", data, 0)
|
|
|
|
if entry[0] != 0:
|
|
out += f"{INDENT}.leaves = &N(tree{num}_Leaves),\n"
|
|
if entry[1] != 0:
|
|
out += f"{INDENT}.trunk = &N(tree{num}_Trunk),\n"
|
|
if entry[2] != 0:
|
|
out += f"{INDENT}.drops = &N(tree{num}_Drops),\n"
|
|
if entry[3] != 0:
|
|
out += f"{INDENT}.vectors = &N(tree{num}_Vectors),\n"
|
|
if entry[4] != 0:
|
|
out += f"{INDENT}.callback = &N(tree{num}_Callback),\n"
|
|
|
|
out += f"}};\n"
|
|
|
|
elif struct["type"] == "TriggerCoord":
|
|
out += f"Vec4f {name} = {{"
|
|
|
|
data = bytes.read(struct["length"])
|
|
entry = unpack_from(">4f", data, 0)
|
|
|
|
out += f" {entry[0]:.01f}f, {entry[1]:.01f}f, {entry[2]:.01f}f, {entry[3]:.01f}f }};\n"
|
|
|
|
elif struct["type"] == "Header":
|
|
out += f"MapSettings N(settings) = {{\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 MapSettings background {bg:X}")
|
|
# out += f" .tattle = 0x{tattle:X},\n"
|
|
INCLUDES_NEEDED["tattle"].append(
|
|
f"- [0x{(tattle & 0xFF0000) >> 16:02X}, 0x{tattle & 0xFFFF:04X}, {map_name}_tattle]"
|
|
)
|
|
out += f" .tattle = {{ MSG_{map_name}_tattle }},\n"
|
|
|
|
out += f"}};\n"
|
|
afterHeader = True
|
|
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"] == "VectorList":
|
|
data = bytes.read(struct["length"])
|
|
if len(data) > 0:
|
|
out += f"Vec3f {name}[] = {{\n"
|
|
out += f"\t"
|
|
for i, pos in enumerate(range(0, len(data), 0xC)):
|
|
x, y, z = unpack_from(">fff", data, pos)
|
|
out += f" {{ {x:.01f}, {y:.01f}, {z:.01f} }},"
|
|
if (i + 1) % 2 == 0:
|
|
out += f"\n\t"
|
|
out += f"\n}};\n"
|
|
|
|
elif struct["type"] == "Formation":
|
|
out += f"Formation {struct['name']} = {{\n"
|
|
|
|
num_bytes_remaining = struct["length"]
|
|
while num_bytes_remaining > 0:
|
|
actor, position, priority, var0, var1, var2, var3 = unpack(">IIIIIII", bytes.read(0x1C))
|
|
num_bytes_remaining -= 0x1C
|
|
|
|
out += " { "
|
|
|
|
if actor in symbol_map:
|
|
out += f".actor = &{symbol_map[actor][0][1]}, "
|
|
|
|
s = f"ActorBlueprint {symbol_map[actor][0][1]};"
|
|
if s not in INCLUDES_NEEDED["forward"]:
|
|
INCLUDES_NEEDED["forward"].append(s)
|
|
else:
|
|
out += f".actor = {actor}, "
|
|
|
|
if position in symbol_map:
|
|
out += f".home = {{ .vec = &{symbol_map[position][0][1]} }}"
|
|
|
|
s = f"Vec3f {symbol_map[position][0][1]};"
|
|
if s not in INCLUDES_NEEDED["forward"]:
|
|
INCLUDES_NEEDED["forward"].append(s)
|
|
else:
|
|
out += f".home = {{ .index = {position} }}"
|
|
|
|
out += f", .priority = {priority}"
|
|
|
|
if var0 == 0 and var1 == 0 and var2 == 0 and var3 == 0:
|
|
pass
|
|
else:
|
|
out += f", {var0}, {var1} {var2}, {var3}"
|
|
|
|
out += " },\n"
|
|
|
|
out += f"}};\n"
|
|
elif struct["type"] == "FormationTable":
|
|
out += f"BattleList {struct['name']} = {{\n"
|
|
|
|
num_bytes_remaining = struct["length"]
|
|
while num_bytes_remaining > 0:
|
|
name, formation_length, ptr, stage_ptr, zero = unpack(">IIIII", bytes.read(4 * 5))
|
|
num_bytes_remaining -= 4 * 5
|
|
|
|
if name == 0:
|
|
out += " {},\n"
|
|
else:
|
|
out += " BATTLE("
|
|
out += f"{symbol_map[name][0][1]}, "
|
|
out += f"{symbol_map[ptr][0][1]}, "
|
|
out += f"&{symbol_map[stage_ptr][0][1]}"
|
|
out += "),\n"
|
|
|
|
out += f"}};\n"
|
|
elif struct["type"] == "StageTable":
|
|
out += f"StageList {struct['name']} = {{\n"
|
|
|
|
num_bytes_remaining = struct["length"]
|
|
while num_bytes_remaining > 0:
|
|
name, ptr = unpack(">II", bytes.read(0x8))
|
|
num_bytes_remaining -= 0x8
|
|
|
|
if name == 0 and ptr == 0:
|
|
out += " {},\n"
|
|
else:
|
|
out += " { "
|
|
out += f"{symbol_map[name][0][1]}, "
|
|
out += f"&{symbol_map[ptr][0][1]} "
|
|
out += "},\n"
|
|
|
|
out += f"}};\n"
|
|
elif struct["type"] == "DefenseTable":
|
|
out += f"s32 {struct['name']}[] = {{\n"
|
|
|
|
num_bytes_remaining = struct["length"]
|
|
while num_bytes_remaining > 0:
|
|
if num_bytes_remaining == 4:
|
|
bytes.read(0x4)
|
|
out += " ELEMENT_END,\n"
|
|
break
|
|
|
|
element, value = unpack(">II", bytes.read(0x8))
|
|
num_bytes_remaining -= 0x8
|
|
|
|
element = disasm_script.CONSTANTS["Elements"][element]
|
|
|
|
out += f" {element}, "
|
|
out += f"{value},\n"
|
|
|
|
out += f"}};\n"
|
|
elif struct["type"] == "StatusTable":
|
|
out += f"s32 {struct['name']}[] = {{\n"
|
|
|
|
num_bytes_remaining = struct["length"]
|
|
while num_bytes_remaining > 0:
|
|
if num_bytes_remaining == 4:
|
|
bytes.read(0x4)
|
|
out += " STATUS_END,\n"
|
|
break
|
|
|
|
element, value = unpack(">Ii", bytes.read(0x8))
|
|
num_bytes_remaining -= 0x8
|
|
|
|
element = disasm_script.CONSTANTS["Statuses"][element]
|
|
|
|
out += f" {element}, "
|
|
out += f"{value},\n"
|
|
|
|
out += f"}};\n"
|
|
elif struct["type"] == "IdleAnimations":
|
|
out += f"s32 {struct['name']}[] = {{\n"
|
|
|
|
num_bytes_remaining = struct["length"]
|
|
while num_bytes_remaining > 0:
|
|
if num_bytes_remaining == 4:
|
|
bytes.read(0x4)
|
|
out += " STATUS_END,\n"
|
|
break
|
|
|
|
element, anim = unpack(">II", bytes.read(0x8))
|
|
num_bytes_remaining -= 0x8
|
|
|
|
element = disasm_script.CONSTANTS["Statuses"][element]
|
|
|
|
value = anim & 0x00FFFFFF
|
|
|
|
if value in disasm_script.CONSTANTS["NPC_SPRITE"]:
|
|
INCLUDES_NEEDED["sprites"].add(disasm_script.CONSTANTS["NPC_SPRITE"][str(value) + ".h"])
|
|
anim = disasm_script.CONSTANTS["NPC_SPRITE"][value]
|
|
else:
|
|
anim = f"{anim:06X}"
|
|
|
|
out += f" {element}, "
|
|
out += " " * (16 - len(element))
|
|
out += f"{anim},\n"
|
|
|
|
out += f"}};\n"
|
|
elif struct["type"] == "ActorParts":
|
|
out += f"ActorPartBlueprint {struct['name']}[] = {{\n"
|
|
|
|
for _ in range(0, struct["length"] // 36):
|
|
d = unpack(">IbbbbbbhIIIIbbxxxxxx", bytes.read(36))
|
|
|
|
out += INDENT + "{\n"
|
|
out += INDENT + INDENT + f".flags = {read_flags(d[0], 'ActorPartFlags')},\n"
|
|
out += INDENT + INDENT + f".index = {d[1]},\n"
|
|
out += INDENT + INDENT + f".posOffset = {{ {d[2]}, {d[3]}, {d[4]} }},\n"
|
|
out += INDENT + INDENT + f".targetOffset = {{ {d[5]}, {d[6]} }},\n"
|
|
out += INDENT + INDENT + f".opacity = {d[7]},\n"
|
|
out += INDENT + INDENT + f".idleAnimations = N(IdleAnimations_{d[8]:08X}),\n"
|
|
out += INDENT + INDENT + f".defenseTable = N(DefenseTable_{d[9]:08X}),\n"
|
|
out += INDENT + INDENT + f".eventFlags = {read_flags(d[10], 'ActorEventFlags')},\n"
|
|
out += INDENT + INDENT + f".elementImmunityFlags = {read_flags(d[11], 'ElementImmunityFlags')},\n"
|
|
out += INDENT + INDENT + f".unk_1C = {d[12]},\n"
|
|
out += INDENT + INDENT + f".unk_1D = {d[13]},\n"
|
|
out += INDENT + "},\n"
|
|
|
|
out += f"}};\n"
|
|
elif struct["type"] == "Actor":
|
|
out += f"ActorBlueprint NAMESPACE = {{\n"
|
|
|
|
d = unpack(">IxBBBhxxIIIBBBBBBBBbbbbbbbb", bytes.read(struct["length"]))
|
|
|
|
out += INDENT + f".flags = {read_flags(d[0], 'ActorFlags')},\n"
|
|
out += INDENT + f".type = {read_enum(d[1], 'ActorType')},\n"
|
|
out += INDENT + f".level = {d[2]},\n"
|
|
out += INDENT + f".maxHP = {d[3]},\n"
|
|
out += INDENT + f".partCount = ARRAY_COUNT({read_ptr(d[5], symbol_map)}),\n"
|
|
out += INDENT + f".partsData = {read_ptr(d[5], symbol_map)},\n"
|
|
out += INDENT + f".initScript = {read_ptr(d[6], symbol_map)},\n"
|
|
out += INDENT + f".statusTable = {read_ptr(d[7], symbol_map)},\n"
|
|
out += INDENT + f".escapeChance = {d[8]},\n"
|
|
out += INDENT + f".airLiftChance = {d[9]},\n"
|
|
out += INDENT + f".hurricaneChance = {d[10]},\n"
|
|
out += INDENT + f".spookChance = {d[11]},\n"
|
|
out += INDENT + f".upAndAwayChance = {d[12]},\n"
|
|
out += INDENT + f".spinSmashReq = {d[13]},\n"
|
|
out += INDENT + f".powerBounceChance = {d[14]},\n"
|
|
out += INDENT + f".coinReward = {d[15]},\n"
|
|
out += INDENT + f".size = {{ {d[16]}, {d[17]} }},\n"
|
|
out += INDENT + f".healthBarOffset = {{ {d[18]}, {d[19]} }},\n"
|
|
out += INDENT + f".statusIconOffset = {{ {d[20]}, {d[21]} }},\n"
|
|
out += INDENT + f".statusTextOffset = {{ {d[22]}, {d[23]} }},\n"
|
|
|
|
out += f"}};\n"
|
|
|
|
pass
|
|
elif struct["type"] == "Stage":
|
|
out += f"Stage NAMESPACE = {{\n"
|
|
|
|
(
|
|
texture,
|
|
shape,
|
|
hit,
|
|
preBattle,
|
|
postBattle,
|
|
bg,
|
|
unk_18,
|
|
unk_1C,
|
|
unk_20,
|
|
unk_24,
|
|
) = unpack(">IIIIIIIIII", bytes.read(struct["length"]))
|
|
|
|
if texture != 0:
|
|
out += f" .texture = {symbol_map[texture][0][1]},\n"
|
|
|
|
if shape != 0:
|
|
out += f" .shape = {symbol_map[shape][0][1]},\n"
|
|
|
|
if hit != 0:
|
|
out += f" .hit = {symbol_map[hit][0][1]},\n"
|
|
|
|
if bg != 0:
|
|
out += f" .bg = {symbol_map[bg][0][1]},\n"
|
|
|
|
if preBattle != 0:
|
|
out += f" .preBattle = {symbol_map[preBattle][0][1]},\n"
|
|
|
|
if postBattle != 0:
|
|
out += f" .postBattle = {symbol_map[postBattle][0][1]},\n"
|
|
|
|
if unk_18 != 0:
|
|
out += f" .foregroundModelList = {symbol_map[unk_18][0][1]},\n"
|
|
|
|
if unk_1C != 0:
|
|
out += f" .unk_1C = {unk_1C:X},\n"
|
|
|
|
if unk_20 != 0:
|
|
out += f" .unk_20 = {unk_20:X},\n"
|
|
|
|
if unk_24 != 0:
|
|
out += f" .unk_24 = {unk_24:X},\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 or len(s) == 6:
|
|
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,
|
|
}
|
|
)
|
|
else:
|
|
raise Exception(str(s))
|
|
|
|
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("namespace", nargs="?", help="Value of NAMESPACE macro")
|
|
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}"
|
|
is_battle = False
|
|
else:
|
|
battle_area = "_".join(base.lower().split(" ")[1:])
|
|
|
|
if "partner" in base:
|
|
segment_name = f"battle/partners/{battle_area}"
|
|
elif "/starpower/src/" in args.idxfile:
|
|
segment_name = (
|
|
f"battle/star/{battle_area}".replace("starstorm", "star_storm")
|
|
.replace("chillout", "chill_out")
|
|
.replace("timeout", "time_out")
|
|
.replace("upandaway", "up_and_away")
|
|
.replace("starbeam", "star_beam")
|
|
.replace("peachbeam", "peach_beam")
|
|
.replace("peachfocus", "peach_focus")
|
|
.replace("peachdash", "peach_dash")
|
|
)
|
|
else:
|
|
segment_name = f"battle/{battle_area}"
|
|
|
|
is_battle = True
|
|
|
|
symbol_map = disasm_script.script_lib()
|
|
|
|
def add_to_symbol_map(addr, pair):
|
|
if addr in symbol_map:
|
|
symbol_map[addr].append(pair)
|
|
else:
|
|
symbol_map[addr] = [pair]
|
|
|
|
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("dir") == segment_name or segment.get("name") == segment_name
|
|
):
|
|
rom_offset = segment["start"]
|
|
vram = segment["vram"]
|
|
break
|
|
|
|
if rom_offset == -1:
|
|
print(f"can't find segment with dir or name '{segment_name}' in splat.yaml")
|
|
exit(1)
|
|
|
|
if ext == ".midx":
|
|
function_replacements = get_function_list(area_name, map_name, rom_offset)
|
|
INCLUDED["includes"] = get_include_list(area_name, map_name)
|
|
else:
|
|
function_replacements = {}
|
|
|
|
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)
|
|
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], string_literal])
|
|
elif struct["type"] == "SJIS":
|
|
string_data = sjis.decode(romfile.read(struct["length"]))
|
|
|
|
string_literal = '"' + string_data + '"'
|
|
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], string_literal])
|
|
elif struct["type"] == "ConstDouble":
|
|
double = unpack_from(">d", romfile.read(struct["length"]), 0)[0]
|
|
double_literal = f"{double}"
|
|
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], double_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
|
|
add_to_symbol_map(struct["vaddr"], [struct["vaddr"], struct["name"]])
|
|
else:
|
|
add_to_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,
|
|
namespace=args.namespace,
|
|
)
|
|
|
|
print("========== Includes needed: ===========\n")
|
|
if is_battle:
|
|
print(f'#include "battle/battle.h"')
|
|
else:
|
|
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}"')
|
|
print()
|
|
|
|
if INCLUDES_NEEDED["forward"]:
|
|
print("========== Forward declares: ==========\n")
|
|
for forward in sorted(INCLUDES_NEEDED["forward"]):
|
|
if not (forward.startswith("ApiStatus") or forward.startswith("void")):
|
|
print("extern " + forward)
|
|
else:
|
|
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(x) for x in INCLUDES_NEEDED["tattle"]]
|
|
print()
|
|
|
|
print("=======================================\n")
|
|
print(disasm.rstrip())
|