#! /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
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"] == "PartsTable":
            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".hpBarOffset = {{ {d[18]}, {d[19]} }},\n"
            out += INDENT + f".statusIconOffset = {{ {d[20]}, {d[21]} }},\n"
            out += INDENT + f".statusMessageOffset = {{ {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())
