papermario/tools/star_rod_idx_to_c.py
alex 683d0857ff
Fix many compiler & linker warnings (#281)
* fix ld warnings

* fix NpcSettings warn

* get_screen_coords

* various

* bloop

* update INSTALL.md for versioned baseroms
2021-04-25 03:56:22 +09:00

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