papermario/tools/star_rod_idx_to_c.py
lshamis ae66312d8c
Add Python linter to github actions (#1100)
* 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>
2023-07-30 02:03:17 +09:00

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())