#!/usr/bin/env python3

import sym_info
from pathlib import Path


_script_lib = None


def script_lib(offset=0):
    global _script_lib

    if not _script_lib:
        _script_lib = {}

        from os import path
        import re

        # star rod database
        """
        LIB_LINE_RE = re.compile(r"\s+:\s+")
        NAME_RE = re.compile(r"({[^}]*})?\s*([a-zA-Z0-9_]+)")
        for filename in Path(path.dirname(__file__), "star-rod", "database").rglob("*.lib"):
            with open(filename, "r") as file:
                for line in file.readlines():
                    parts = LIB_LINE_RE.split(line)
                    if len(parts) >= 3:
                        try:
                            kind = parts[0]
                            vaddr = int(parts[1].split(", ")[0], 16)
                            if name := NAME_RE.match(parts[2]):
                                name = name.group(2)
                                _script_lib[vaddr] = name
                        except:
                            pass
        """

        repo_root = Path(__file__).resolve().parent.parent
        symbols = Path(repo_root / "ver" / "current" / "symbol_addrs.txt")
        with open(symbols, "r") as file:
            for line in file.readlines():
                s = [s.strip() for s in line.split("=", 1)]
                name = s[0]
                vaddr = int(s[1].split(";")[0].split(" ")[0], 16)

                raddr = "0xFFFFFFFF"
                if "rom:" in line:
                    raddr = line.split("rom:", 1)[1]
                    if " " in raddr:
                        raddr = raddr.split(" ", 1)[0]
                raddr = raddr.strip()

                if vaddr not in _script_lib:
                    _script_lib[vaddr] = []
                _script_lib[vaddr].append([int(raddr, 16), name])

        """
        # Sort the symbols for each vram address by the difference
        # between their rom address and the offset passed in.
        # If offset - rom address goes below 0, it's part of the
        # previous file, so treat it as min priority, same as a default.
        # After sorting, the first rom address and name should be the best candidate.
        for k in _script_lib.keys():
            for i,entry in enumerate(_script_lib[k]):
                diff = offset - entry[0]
                entry[0] = 0xFFFFFFFF if diff < 0 else diff
                _script_lib[k][i][0] = entry[0]
            _script_lib[k] = sorted(_script_lib[k], key=lambda x: x[0])
        """
    return _script_lib


def extend_symbol_map(a, b):
    for k in b:
        if k not in a:
            a[k] = b[k]
        else:
            a[k] += b[k]

    return a


def round_fixed(f: float) -> float:
    g = f * 100.0
    whole = round(g)
    if abs(g - whole) <= 100.0 / 1024.0:
        f = whole / 100.0
    return f


def find_symbol_in_overlay(symbol_map, overlay_rom_addr, symbol_ram_addr):
    if not symbol_ram_addr in symbol_map:
        return None

    lowest_delta = None
    lowest_symbol_name = None

    for rom_addr, symbol_name in symbol_map[symbol_ram_addr]:
        delta = rom_addr - overlay_rom_addr

        if delta >= 0 and (lowest_delta is None or delta <= lowest_delta):
            lowest_delta = delta
            lowest_symbol_name = symbol_name

    if lowest_symbol_name:
        return lowest_symbol_name

    return symbol_map[symbol_ram_addr][0][1]


def browse_header(valid_enums, enums):
    # enums
    for i, line in enumerate(enums):
        if (line.startswith("enum ")) or (line.startswith("typedef enum ")):
            enum_name = line.split("enum ", 1)[1].split(" {", 1)[0]
            if enum_name in valid_enums:
                CONSTANTS[enum_name] = {}
                last_num = -1
                i += 1
                while "}" not in enums[i]:
                    if not enums[i]:
                        i += 1
                        continue

                    if "//" in enums[i]:
                        name = enums[i].split("//", 1)[0].strip()
                    else:
                        name = enums[i].strip()
                    val = last_num + 1
                    if "=" in name:
                        name, val = name.split(" = ")
                        val = int(val[:-1], 0)
                        if val > 0x80000000:
                            val -= 0x100000000
                    else:
                        name = name[:-1]
                    name = name.strip()
                    # print("\"" + name + "\"", "===", val)

                    CONSTANTS[enum_name][val] = name.strip()
                    i += 1
                    last_num = val
    return


# Grab CONSTANTS from the include/ folder to save manual work
CONSTANTS = {}
SWITCH_TYPES = []
LOCAL_WORDS = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
SAVE_VARS = set()


def get_constants():
    global CONSTANTS
    global VALID_SAVE_VARS

    valid_enums = {
        "StoryProgress",
        "ItemIDs",
        "PlayerAnims",
        "ActorType",
        "ActorIDs",
        "Events",
        "SoundIDs",
        "SongIDs",
        "Locations",
        "AmbientSounds",
        "NpcIDs",
        "Emotes",
        "NpcFlags",
        "Statuses",
        "Elements",
        "DamageTypes",
        "ElementImmunityFlags",
        "HitResults",
        "ActorFlags",
        "ActorPartFlags",
        "ActorEventFlags",
        "ElementFlags",
        "EncounterTriggers",
        "Abilities",
        "EasingType",
        "DecorationIDs",
        "HitResults",
        "Phases",
        "ItemSpawnModes",
        "ActionStates",
        "Triggers",
        "Buttons",
        "ActionCommand",
        "MoveIDs",
        "BattleStatusFlags1",
        "BattleStatusFlags2",
        "BtlCameraPreset",
        "EffectID",
        "StatusFlags",
    }
    for enum in valid_enums:
        CONSTANTS[enum] = {}
    CONSTANTS["NPC_SPRITE"] = {}
    CONSTANTS["MAP_NPCS"] = {}

    [SAVE_VARS.add(x) for x in ["GB_WorldLocation", "GB_StoryProgress"]]

    include_path = Path(Path(__file__).resolve().parent.parent / "include")
    enums = Path(include_path / "enums.h").read_text().splitlines()
    browse_header(valid_enums, enums)
    enums = Path(include_path / "effects.h").read_text().splitlines()
    browse_header(valid_enums, enums)

    include_path = Path(Path(__file__).resolve().parent.parent / "src" / "battle")
    enums = Path(include_path / "battle.h").read_text().splitlines()
    browse_header(valid_enums, enums)

    """
    # defines
    for line in enums:
        this_enum = ""
        for enum in valid_defines:
            if f"#define {enum}_" in line:
                this_enum = enum
                break;
        if this_enum:
            name = line.split(" ",2)[1]
            id_ = line.split("0x", 1)[1]
            if " " in id_:
                id_ = id_.split(" ",1)[0]
            CONSTANTS[this_enum][int(id_, 16)] = name
    """

    # exit()
    # sprites
    sprite_path = Path(
        Path(__file__).resolve().parent.parent / "ver" / "current" / "build" / "include" / "sprite" / "npc"
    )
    for file in sprite_path.iterdir():
        fd = file.read_text()
        for line in fd.splitlines():
            if "#define NPC_ANIM_" not in line:
                continue

            header_file_name = file.parts[-1]

            name = line.split(" ", 2)[1]
            value = int(line.split("0x", 1)[1], base=16)

            CONSTANTS["NPC_SPRITE"][value] = name
            CONSTANTS["NPC_SPRITE"][str(value) + ".h"] = header_file_name

    return


def make_anim_macro(self, sprite, palette, anim):
    if sprite == 0xFF and palette == 0xFF and anim == 0xFF:
        return "-1"

    value = (sprite << 16) | (palette << 8) | anim

    if value in CONSTANTS["NPC_SPRITE"]:
        self.INCLUDES_NEEDED["sprites"].add(CONSTANTS["NPC_SPRITE"][str(value) + ".h"])
        return CONSTANTS["NPC_SPRITE"][value]
    else:
        return f"0x{sprite:02X}{palette:02X}{anim:02X}"


def remove_evt_ptr(s):
    if s.startswith("EVT_PTR("):
        return s[8:-1]
    else:
        return s


def make_flags(argNum, flags, new_args):
    enabled = []
    for x in range(32):
        flag = argNum & (1 << x)
        if flag:
            if flag in CONSTANTS[flags]:
                enabled.append(CONSTANTS[flags][flag])
            else:
                enabled.append(f"0x{flag:08X}")
    if not enabled:
        enabled.append(f"0")
    new_args.append(" | ".join(enabled))


def fix_args(self, func, args, info):
    global CONSTANTS

    new_args = []
    args = args.split(", ")
    for i, arg in enumerate(args):
        if (remove_evt_ptr(arg).startswith("D_B")) or (i == 0 and func == "MakeEntity" and arg.startswith("D_")):
            if func == "MakeEntity":
                arg = "MAKE_ENTITY_END"
            else:
                arg = "0x" + remove_evt_ptr(arg)[2:]

        if "0x" in arg and int(remove_evt_ptr(arg), 16) >= 0xF0000000:
            arg = f"{int(remove_evt_ptr(arg), 16) - 0x100000000}"

        if i in info or (i + 1 == len(args) and -1 in info):
            if arg.startswith("LW"):
                argNum = trim_lw(arg)
                LOCAL_WORDS[int(argNum)] = info[i]
                new_args.append(f"{arg}")
                # print(LOCAL_WORDS)
                continue

            if i + 1 == len(args) and -1 in info:
                i = -1
            if "_" in arg:
                new_args.append(f"{arg}")
                continue
            try:
                argNum = int(arg, 0)
            except ValueError:
                new_args.append(f"{arg}")
                continue

            if info[i] == "Bool":
                new_args.append(f"{'TRUE' if argNum == True else 'FALSE'}")
            elif info[i] == "Hex" and argNum > 0:
                new_args.append(f"0x{argNum:08X}")
            elif info[i] == "CustomAnim":
                if argNum != -1:
                    try:
                        value = argNum & 0x00FFFFFF

                        if func == "SetAnimation" and int(new_args[1], 10) == 0:
                            call = f"{CONSTANTS['PlayerAnims'][argNum]}"
                        elif value in CONSTANTS["NPC_SPRITE"]:
                            self.INCLUDES_NEEDED["sprites"].add(CONSTANTS["NPC_SPRITE"][str(value) + ".h"])
                            call = CONSTANTS["NPC_SPRITE"][value]
                        else:
                            call = f"{argNum:06X}"
                    except ValueError:
                        call = f"0x{argNum:06X}"
                    except KeyError:
                        call = f"0x{argNum:06X}"
                else:
                    call = "-1"
                new_args.append(call)
            elif info[i] == "CustomMsg":
                type_ = (argNum & 0xFF0000) >> 16
                num_ = (argNum & 0xFFFF) >> 0
                new_args.append(f"MESSAGE_ID(0x{type_:02X}, 0x{num_:04X})")
            elif info[i] == "NpcFlags":
                make_flags(argNum, "NpcFlags", new_args)
            elif info[i] == "DamageTypes":
                make_flags(argNum, "DamageTypes", new_args)
            elif info[i] == "ActorPartFlags":
                make_flags(argNum, "ActorPartFlags", new_args)
            elif info[i] == "ActorFlags":
                make_flags(argNum, "ActorFlags", new_args)
            elif info[i] == "BattleStatusFlags1":
                make_flags(argNum, "BattleStatusFlags1", new_args)
            elif info[i] == "BattleStatusFlags2":
                make_flags(argNum, "BattleStatusFlags2", new_args)
            elif info[i] == "StatusFlags":
                make_flags(argNum, "StatusFlags", new_args)
            elif info[i] == "NpcIDs":
                if argNum >= 0:
                    if argNum in CONSTANTS["MAP_NPCS"]:
                        new_args.append(CONSTANTS["MAP_NPCS"][argNum])
                    else:
                        new_args.append(str(argNum))
                else:
                    new_args.append(CONSTANTS["NpcIDs"][argNum])
            elif info[i] == "SoundIDs":
                if argNum in CONSTANTS["SoundIDs"]:
                    new_args.append(CONSTANTS["SoundIDs"][argNum])
                else:
                    new_args.append("0x%X" % argNum)
            elif info[i] == "StoryProgress":
                print(info[i])
                if argNum in CONSTANTS["StoryProgress"]:
                    new_args.append(CONSTANTS["StoryProgress"][argNum])
                else:
                    new_args.append(str(argNum))
            else:
                try:
                    new_args.append(f"{CONSTANTS[info[i]][argNum]}")
                except KeyError:
                    if not (info[i] == "NpcIDs" and argNum > 0):
                        print(
                            f"0x{argNum:X} was not found within {info[i]} constants for function {func} arg {i}, add it."
                        )

                    if info[i] == "ItemIDs" and argNum < 0:
                        new_args.append(f"{int(argNum)}")
                    else:
                        # Print the unknowns in hex
                        new_args.append(self.var(argNum))

        else:
            new_args.append(f"{arg}")
    return ", ".join(new_args)


replace_funcs = {
    "ActorExists": {0: "ActorIDs", 1: "Bool"},
    "ActorSpeak": {0: "CustomMsg", 1: "ActorIDs", 3: "CustomAnim", 4: "CustomAnim"},
    "AddActorDecoration": {0: "ActorIDs"},
    "AddActorVar": {0: "ActorIDs"},
    "AddKeyItem": {0: "ItemIDs"},
    "AddGoalPos": {0: "ActorIDs"},
    "BattleCamTargetActor": {0: "ActorIDs"},
    "BindHandleEvent": {0: "ActorIDs"},
    "BindIdle": {0: "ActorIDs"},
    "BindHandlePhase": {0: "ActorIDs"},
    "BindNpcAI": {0: "NpcIDs"},
    "BindNpcDefeat": {0: "NpcIDs"},
    "BindNpcIdle": {0: "NpcIDs"},
    "BindNpcInteract": {0: "NpcIDs"},
    "BindTakeTurn": {0: "ActorIDs"},
    "CheckButtonDown": {0: "Hex", 1: "Bool"},
    "ContinueSpeech": {1: "CustomAnim", 2: "CustomAnim", 4: "CustomMsg"},
    "CopyBuffs": {0: "ActorIDs", 1: "ActorIDs"},
    "CopyStatusEffects": {0: "ActorIDs", 1: "ActorIDs"},
    "CountTargets": {0: "ActorIDs"},
    "DisablePlayerInput": {0: "Bool"},
    "DisablePlayerPhysics": {0: "Bool"},
    "DispatchDamagePlayerEvent": {1: "Events"},
    "DispatchEvent": {0: "ActorIDs", 1: "Events"},
    "EnableActorBlur": {0: "ActorIDs"},
    "EnableActorGlow": {0: "ActorIDs", 1: "Bool"},
    "EnableIdleScript": {0: "ActorIDs"},
    "EnableNpcShadow": {0: "NpcIDs", 1: "Bool"},
    "EndActorSpeech": {0: "ActorIDs", 2: "CustomAnim", 3: "CustomAnim"},
    "EndSpeech": {1: "CustomAnim", 2: "CustomAnim"},
    "EnemyDamageTarget": {
        0: "ActorIDs",
        1: "HitResults",
        2: "DamageTypes",
        4: "StatusFlags",
        6: "BattleStatusFlags1",
    },
    "EnemyTestTarget": {
        0: "ActorIDs",
        1: "HitResults",
        2: "DamageTypes",
        3: "StatusFlags",
        5: "BattleStatusFlags1",
    },
    "FallToGoal": {0: "ActorIDs"},
    "FindKeyItem": {0: "ItemIDs"},
    "FlyPartTo": {0: "ActorIDs"},
    "FlyToGoal": {0: "ActorIDs"},
    "ForceHomePos": {0: "ActorIDs"},
    "GetPartAnimNotify": {0: "ActorIDs"},
    "SetActorPaletteEffect": {0: "ActorIDs"},
    "SetActorPaletteSwapParams": {0: "ActorIDs"},
    "EnableActorPaletteEffects": {0: "ActorIDs"},
    "HideHealthBar": {0: "ActorIDs"},
    "func_8027D434": {0: "ActorIDs"},
    "SetProjectileTargetOffset": {0: "ActorIDs"},
    "GetInstigatorValue": {0: "ActorIDs"},
    "SetNpcFoldParams": {0: "NpcIDs"},
    "SetNpcFoldFlags": {0: "NpcIDs"},
    "UpdatePlayerFold": {0: "PlayerAnims"},
    "GetAnimation": {0: "ActorIDs", 2: "CustomAnim"},
    "GetActorFlags": {0: "ActorIDs", 1: "ActorFlags"},
    "GetActorHP": {0: "ActorIDs"},
    "GetActorPos": {0: "ActorIDs"},
    "GetActorSize": {0: "ActorIDs"},
    "GetActorVar": {0: "ActorIDs"},
    "GetBattleFlags": {0: "BattleStatusFlags1"},
    "GetBattlePhase": {0: "Phases"},
    "GetDistanceToGoal": {0: "ActorIDs"},
    "GetEnemyMaxHP": {0: "ActorIDs"},
    "GetGoalPos": {0: "ActorIDs"},
    "GetHomePos": {0: "ActorIDs"},
    "GetIdleGoal": {0: "ActorIDs"},
    "GetIndexFromHome": {0: "ActorIDs"},
    "GetIndexFromPos": {0: "ActorIDs"},
    "GetItemPower": {0: "ItemIDs"},
    "GetLastDamage": {0: "ActorIDs"},
    "GetLastElement": {0: "DamageTypes"},
    "GetLastEvent": {0: "ActorIDs", 1: "Events"},
    "GetPartOffset": {0: "ActorIDs"},
    "GetPartPos": {0: "ActorIDs"},
    "GetPartRotation": {0: "ActorIDs"},
    "GetNpcPos": {0: "NpcIDs"},
    "GetOriginalActorType": {0: "ActorIDs", 1: "ActorType"},
    "GetStatusFlags": {0: "ActorIDs", 1: "StatusFlags"},
    "HidePlayerShadow": {0: "Bool"},
    "HPBarToCurrent": {0: "ActorIDs"},
    "HPBarToHome": {0: "ActorIDs"},
    "IdleFlyToGoal": {0: "ActorIDs"},
    "IdleJumpToGoal": {0: "ActorIDs"},
    "IdleRunToGoal": {0: "ActorIDs"},
    "InterpNpcYaw": {0: "NpcIDs"},
    "JumpPartTo": {0: "ActorIDs"},
    "JumpToGoal": {0: "ActorIDs", 2: "Bool", 3: "Bool", 4: "Bool"},
    "JumpWithBounce": {0: "ActorIDs"},
    "LandJump": {0: "ActorIDs"},
    "LoadActionCommand": {0: "ActionCommand"},
    "MakeEntity": {0: "Hex"},
    "MakeItemEntity": {0: "ItemIDs"},
    "ModifyColliderFlags": {2: "Hex"},
    "NpcFaceNpc": {0: "NpcIDs", 1: "NpcIDs"},
    "NpcFacePlayer": {0: "NpcIDs"},
    "NpcJump0": {0: "NpcIDs"},
    "NpcJump1": {0: "NpcIDs"},
    "NpcMoveTo": {0: "NpcIDs"},
    "PlayAmbientSounds": {0: "AmbientSounds"},
    "PlayEffect": {0: "EffectID"},
    "PlayLoopingSoundAtActor": {0: "ActorIDs", 2: "SoundIDs"},
    "PlaySound": {0: "SoundIDs"},
    "PlaySoundAt": {0: "SoundIDs"},
    "PlaySoundAtActor": {0: "ActorIDs", 1: "SoundIDs"},
    "PlaySoundAtModel": {1: "SoundIDs"},
    "PlaySoundAtNpc": {0: "NpcIDs", 1: "SoundIDs"},
    "PlaySoundAtPart": {0: "ActorIDs", 2: "SoundIDs"},
    "RemoveActor": {0: "ActorIDs"},
    "RemoveActorDecoration": {0: "ActorIDs"},
    "RemoveNpc": {0: "NpcIDs"},
    "ResetActorSounds": {0: "ActorIDs"},
    "ResetAllActorSounds": {0: "ActorIDs"},
    "RunToGoal": {0: "ActorIDs", 2: "Bool"},
    "SetActorDispOffset": {0: "ActorIDs"},
    "SetActorFlagBits": {0: "ActorIDs", 1: "ActorFlags"},
    "SetActorIdleSpeed": {0: "ActorIDs"},
    "SetActorIdleJumpGravity": {0: "ActorIDs"},
    "SetActorJumpGravity": {0: "ActorIDs"},
    "SetActorPos": {0: "ActorIDs"},
    "SetActorRotation": {0: "ActorIDs"},
    "SetActorRotationOffset": {0: "ActorIDs"},
    "SetActorScale": {0: "ActorIDs"},
    "SetActorSize": {0: "ActorIDs"},
    "SetActorSounds": {0: "ActorIDs"},
    "SetActorSpeed": {0: "ActorIDs"},
    "SetActorType": {0: "ActorIDs", 1: "ActorType"},
    "SetActorVar": {0: "ActorIDs"},
    "SetActorYaw": {0: "ActorIDs"},
    "SetAnimation": {0: "ActorIDs", 2: "CustomAnim"},
    "SetAnimationRate": {0: "ActorIDs"},
    "SetBattleFlagBits": {0: "BattleStatusFlags1"},
    "SetBattleFlagBits2": {0: "BattleStatusFlags2"},
    "SetDefenseTable": {0: "ActorIDs"},
    "SetEnemyHP": {0: "ActorIDs"},
    "SetEnemyTargetOffset": {0: "ActorIDs"},
    "SetGoalPos": {0: "ActorIDs"},
    "SetGoalToFirstTarget": {0: "ActorIDs"},
    "SetGoalToHome": {0: "ActorIDs"},
    "SetGoalToIndex": {0: "ActorIDs"},
    "SetGoalToTarget": {0: "ActorIDs"},
    "SetHomePos": {0: "ActorIDs"},
    "SetIdleAnimations": {0: "ActorIDs"},
    "SetIdleGoal": {0: "ActorIDs"},
    "SetIdleGoalToHome": {0: "ActorIDs"},
    "SetJumpAnimations": {
        0: "ActorIDs",
        2: "PlayerAnims",
        3: "PlayerAnims",
        4: "PlayerAnims",
    },
    "SetMusicTrack": {1: "SongIDs"},
    "SetNpcAnimation": {0: "NpcIDs", 1: "CustomAnim"},
    "SetNpcAux": {0: "NpcIDs"},
    "SetNpcFlagBits": {0: "NpcIDs", 1: "NpcFlags", 2: "Bool"},
    "SetNpcJumpscale": {0: "NpcIDs"},
    "SetNpcPos": {0: "NpcIDs"},
    "SetNpcRotation": {0: "NpcIDs"},
    "SetNpcScale": {0: "NpcIDs"},
    "SetNpcSpeed": {0: "NpcIDs"},
    "SetNpcSprite": {1: "Hex"},
    "SetNpcYaw": {0: "NpcIDs"},
    "SetOwnerID": {0: "ActorIDs"},
    "SetPartAlpha": {0: "ActorIDs"},
    "SetPartDispOffset": {0: "ActorIDs"},
    "SetPartEventBits": {0: "ActorIDs", 2: "ActorEventFlags"},
    "SetPartFlags": {0: "ActorIDs", 2: "ActorPartFlags"},
    "SetPartFlagBits": {0: "ActorIDs", 2: "ActorPartFlags"},
    "SetPartJumpGravity": {0: "ActorIDs"},
    "SetPartMoveSpeed": {0: "ActorIDs"},
    "SetPartPos": {0: "ActorIDs"},
    "SetPartRotation": {0: "ActorIDs"},
    "SetPartRotationOffset": {0: "ActorIDs"},
    "SetPartScale": {0: "ActorIDs"},
    "SetPartSize": {0: "ActorIDs"},
    "SetPartSounds": {0: "ActorIDs"},
    "SetPartTargetFlagBits": {0: "ActorIDs"},
    "SetPartYaw": {0: "ActorIDs"},
    "SetPlayerAnimation": {0: "PlayerAnims"},
    "SetSelfEnemyFlagBits": {0: "NpcFlags", 1: "Bool"},
    # "SetSelfVar"                :{1:"Bool"}, # apparently this was a bool in some scripts but it passes non-0/1 values, including negatives
    "SetSpriteShading": {0: "CustomAnim"},
    "SetStatusTable": {0: "ActorIDs"},
    "SetTargetActor": {0: "ActorIDs", 1: "ActorIDs"},
    "SetTargetOffset": {0: "ActorIDs"},
    "ShowChoice": {0: "CustomMsg"},
    "ShowEmote": {1: "Emotes"},
    "ShowMessageAtScreenPos": {0: "CustomMsg"},
    "ShowMessageAtWorldPos": {0: "CustomMsg"},
    "SpeakToPlayer": {0: "NpcIDs", 1: "CustomAnim", 2: "CustomAnim", -1: "CustomMsg"},
    "StopLoopingSoundAtActor": {0: "ActorIDs"},
    "SwitchMessage": {0: "CustomMsg"},
    "UseBattleCamPreset": {0: "BtlCameraPreset"},
    "UseIdleAnimation": {0: "ActorIDs", 1: "Bool"},
    "WasStatusInflicted": {0: "ActorIDs", 1: "Bool"},
}


def trim_lw(arg):
    return arg[arg.find("(") + 1 : arg.find(")")]


def replace_constants(self, func, args):
    global replace_funcs
    global LOCAL_WORDS

    new_args = []
    new_args = args.split(", ")
    for i, new_arg in enumerate(new_args):
        if new_arg.startswith("LW"):
            new_arg = trim_lw(new_arg)
            LOCAL_WORDS[int(new_arg)] = 0

    if func in replace_funcs:
        return fix_args(self, func, args, replace_funcs[func])

    return args


def fix_1_arg(self, lw, arg, format):
    if lw:
        lw = int(trim_lw(lw))
        if LOCAL_WORDS[lw]:
            info = {0: LOCAL_WORDS[lw]}
            args = f"{arg}"
            return fix_args(self, 0, args, info)

    if format:
        info = {0: format}
        args = f"{arg}"
        return fix_args(self, 0, args, info)

    return arg


class ScriptDisassembler:
    def __init__(
        self,
        bytes,
        script_name="script",
        symbol_map={},
        romstart=0,
        INCLUDES_NEEDED={"forward": [], "sprites": set(), "npcs": []},
        INCLUDED={"functions": set(), "includes": set()},
        prelude=True,
        transform_symbol_name=None,
        use_script_lib=True,
    ):
        self.bytes = bytes
        self.script_name = script_name
        self.prelude = prelude

        if use_script_lib:
            self.symbol_map = extend_symbol_map(symbol_map, script_lib(self.bytes.tell()))
        else:
            self.symbol_map = symbol_map

        self.romstart = romstart
        self.transform_symbol_name = transform_symbol_name
        self.INCLUDES_NEEDED = INCLUDES_NEEDED
        self.INCLUDED = INCLUDED

        self.out = ""
        self.prefix = ""

        self.indent = 1
        self.indent_used = False

        self.done = False

        self.start_pos = self.bytes.tell()
        self.end_pos = 0
        self.instructions = 0

    def disassemble(self):
        while True:
            opcode = self.read_word()
            argc = self.read_word()

            # print(f"Op {opcode:X}, argc {argc}")

            if opcode > 0xFF or argc > 0xFF:
                raise Exception(f"script '{self.script_name}' is malformed (opcode {opcode:X}, argc {argc:X})")

            argv = []
            for i in range(0, argc):
                argv.append(self.read_word())

            # print(argv)

            self.disassemble_command(opcode, argc, argv)

            self.instructions += 1

            if self.done:
                self.end_pos = self.bytes.tell()
                return self.prefix + self.out

    def write(self, line):
        if self.indent < 0:
            self.indent = 0
        if self.indent > 1:
            self.indent_used = True

        self.out += "    " * self.indent
        self.out += line

    def write_line(self, line):
        self.write(line)
        self.out += "\n"

    def prefix_line(self, line):
        self.prefix += line
        self.prefix += "\n"

    def var(self, arg, prefer_hex=False, use_evt_ptr=True):
        if arg in self.symbol_map:
            s = self.symbol_map[arg][0][1]
            return f"EVT_PTR({s})" if use_evt_ptr else s

        v = arg - 2**32  # convert to s32
        if v > -250000000:
            if v <= -220000000:
                return f"EVT_FLOAT({round_fixed((v + 230000000) / 1024)})"
            elif v <= -200000000:
                return f"ArrayFlag({v + 210000000})"
            elif v <= -180000000:
                return f"ArrayVar({v + 190000000})"
            elif v <= -160000000:
                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}"
        elif prefer_hex and arg > 0:
            return f"0x{arg:X}"
        else:
            return f"{arg}"

    def replace_star_rod_function_name(self, name):
        vram = int(name.split("_", 1)[1], 16)
        name = "N(" + name.replace("function", "func") + f"_{(vram - 0x80240000)+self.romstart:X}" + ")"
        return name

    def replace_star_rod_prefix(self, addr, isArg=False):
        if type(addr) is str:
            return addr
        if addr > 0x80000000 and addr in self.symbol_map:
            name = find_symbol_in_overlay(self.symbol_map, self.romstart, addr)
            if self.transform_symbol_name:
                name = self.transform_symbol_name(name)
            toReplace = True
            suffix = ""
            if False and name.startswith("N(func_"):
                prefix = "ApiStatus "
                name = self.replace_star_rod_function_name(name[2:-1])
                suffix = "(Evt* script, s32 isInitialCall)"
            elif name[2:-1] in self.INCLUDED["includes"]:
                prefix = "ApiStatus "
                suffix = "(Evt* script, s32 isInitialCall)"
            elif name.startswith("N(npcAISettings_"):
                prefix = "MobileAISettings "
            elif name.startswith("N(npcSettings_"):
                prefix = "NpcSettings "
            elif name.startswith("N(npcGroup_"):
                prefix = "NpcData "
            elif name.startswith("N(entryList_"):
                prefix = "EntryList "
            elif name.startswith("N(npcGroupList_"):
                prefix = "NpcGroupList "
            elif name.startswith("N("):
                prefix = "EvtScript "
            else:
                toReplace = False

            if toReplace:
                if name not in self.INCLUDED["functions"]:
                    self.INCLUDES_NEEDED["forward"].append(prefix + name + suffix + ";")
                    self.INCLUDED["functions"].add(name)
                return name
            elif not isArg or name.startswith('"'):
                return name
            else:
                return str(addr)
        return addr

    def addr_ref(self, addr, isArg=False):
        if addr in self.symbol_map:
            return self.replace_star_rod_prefix(addr, isArg)
        return self.var(addr)  # f"0x{addr:08X}"

    def trigger(self, trigger):
        if trigger == 0x00000040:
            trigger = "TRIGGER_WALL_PUSH"
        if trigger == 0x00000080:
            trigger = "TRIGGER_FLOOR_TOUCH"
        if trigger == 0x00000100:
            trigger = "TRIGGER_WALL_PRESS_A"
        if trigger == 0x00000200:
            trigger = "TRIGGER_FLOOR_JUMP"
        if trigger == 0x00000400:
            trigger = "TRIGGER_WALL_TOUCH"
        if trigger == 0x00000800:
            trigger = "TRIGGER_FLOOR_PRESS_A"
        if trigger == 0x00001000:
            trigger = "TRIGGER_WALL_HAMMER"
        if trigger == 0x00010000:
            trigger = "TRIGGER_GAME_FLAG_SET"
        if trigger == 0x00020000:
            trigger = "TRIGGER_AREA_FLAG_SET"
        if trigger == 0x00040000:
            trigger = "TRIGGER_CEILING_TOUCH"
        if trigger == 0x00080000:
            trigger = "TRIGGER_FLOOR_ABOVE"
        if trigger == 0x00100000:
            trigger = "TRIGGER_POINT_BOMB"
        return f"0x{trigger:X}" if type(trigger) is int else trigger

    def read_word(self):
        return int.from_bytes(self.bytes.read(4), byteorder="big")

    def disassemble_command(self, opcode, argc, argv):
        if opcode == 0x01:
            self.write_line("EVT_END")
            self.indent -= 1

            if self.prelude:
                try:
                    self.prefix_line(
                        f"EvtScript D_{self.script_name - info[0] + info[2]:08X}_{self.script_name:06X} = {{"
                    )
                    self.write_line("};")
                except:
                    self.prefix_line(f"EvtScript {self.script_name} = {{")
                    self.write_line("};")
            self.done = True
        elif opcode == 0x02:
            self.write_line(f"EVT_RETURN")
        elif opcode == 0x03:
            self.write_line(f"EVT_LABEL({self.var(argv[0])})")
        elif opcode == 0x04:
            self.write_line(f"EVT_GOTO({self.var(argv[0])})")
        elif opcode == 0x05:
            self.write_line(f"EVT_LOOP({self.var(argv[0])})")
            self.indent += 1
        elif opcode == 0x06:
            self.indent -= 1
            self.write_line("EVT_END_LOOP")
        elif opcode == 0x07:
            self.write_line(f"EVT_BREAK_LOOP")
        elif opcode == 0x08:
            self.write_line(f"EVT_WAIT({self.var(argv[0])})")
        elif opcode == 0x09:
            self.write_line(f"EVT_WAIT_SECS({self.var(argv[0])})")
        elif opcode == 0x0A:
            if self.var(argv[0]).startswith("LW"):
                args_str = fix_1_arg(self, self.var(argv[0]), self.var(argv[1]), 0)
            else:
                args_str = self.var(argv[1])
            self.write_line(f"EVT_IF_EQ({self.var(argv[0])}, {args_str})")
            self.indent += 1
        elif opcode == 0x0B:
            if self.var(argv[0]).startswith("LW"):
                args_str = fix_1_arg(self, self.var(argv[0]), self.var(argv[1]), 0)
            else:
                args_str = self.var(argv[1])
            self.write_line(f"EVT_IF_NE({self.var(argv[0])}, {args_str})")
            self.indent += 1
        elif opcode == 0x0C:
            self.write_line(f"EVT_IF_LT({self.var(argv[0])}, {self.var(argv[1])})")
            self.indent += 1
        elif opcode == 0x0D:
            self.write_line(f"EVT_IF_GT({self.var(argv[0])}, {self.var(argv[1])})")
            self.indent += 1
        elif opcode == 0x0E:
            self.write_line(f"EVT_IF_LE({self.var(argv[0])}, {self.var(argv[1])})")
            self.indent += 1
        elif opcode == 0x0F:
            self.write_line(f"EVT_IF_GE({self.var(argv[0])}, {self.var(argv[1])})")
            self.indent += 1
        elif opcode == 0x10:
            args_str = fix_1_arg(self, self.var(argv[0]), self.var(argv[1]), "Hex")
            self.write_line(f"EVT_IF_FLAG({self.var(argv[0])}, {args_str})")
            self.indent += 1
        elif opcode == 0x11:
            args_str = fix_1_arg(self, self.var(argv[0]), self.var(argv[1]), "Hex")
            self.write_line(f"EVT_IF_NOT_FLAG({self.var(argv[0])}, {args_str})")
            self.indent += 1
        elif opcode == 0x12:
            self.indent -= 1
            self.write_line(f"EVT_ELSE")
            self.indent += 1
        elif opcode == 0x13:
            self.indent -= 1
            self.write_line(f"EVT_END_IF")
        elif opcode == 0x14:
            new_arg = trim_lw(self.var(argv[0]))
            SWITCH_TYPES.append(LOCAL_WORDS[int(new_arg)])
            self.write_line(f"EVT_SWITCH({self.var(argv[0])})")
            self.indent += 2
        elif opcode == 0x15:
            self.write_line(f"EVT_SWITCH_CONST(0x{argv[0]:X})")
            self.indent += 2
        elif opcode == 0x16:
            self.indent -= 1
            args_str = fix_1_arg(self, 0, self.var(argv[0]), SWITCH_TYPES[-1])
            self.write_line(f"EVT_CASE_EQ({args_str})")
            self.indent += 1
        elif opcode == 0x17:
            self.indent -= 1
            args_str = fix_1_arg(self, 0, self.var(argv[0]), SWITCH_TYPES[-1])
            self.write_line(f"EVT_CASE_NE({args_str})")
            self.indent += 1
        elif opcode == 0x18:
            self.indent -= 1
            self.write_line(f"EVT_CASE_LT({self.var(argv[0])})")
            self.indent += 1
        elif opcode == 0x19:
            self.indent -= 1
            self.write_line(f"EVT_CASE_GT({self.var(argv[0])})")
            self.indent += 1
        elif opcode == 0x1A:
            self.indent -= 1
            self.write_line(f"EVT_CASE_LE({self.var(argv[0])})")
            self.indent += 1
        elif opcode == 0x1B:
            self.indent -= 1
            self.write_line(f"EVT_CASE_GE({self.var(argv[0])})")
            self.indent += 1
        elif opcode == 0x1C:
            self.indent -= 1
            self.write_line(f"EVT_CASE_DEFAULT")
            self.indent += 1
        elif opcode == 0x1D:
            self.indent -= 1
            args_str = fix_1_arg(self, 0, self.var(argv[0]), SWITCH_TYPES[-1])
            self.write_line(f"EVT_CASE_OR_EQ({args_str})")
            self.indent += 1
        elif opcode == 0x1E:
            self.indent -= 1
            args_str = fix_1_arg(self, 0, self.var(argv[0]), SWITCH_TYPES[-1])
            self.write_line(f"EVT_CASE_AND_EQ({args_str})")
            self.indent += 1
        elif opcode == 0x1F:
            self.indent -= 1
            args_str = fix_1_arg(self, 0, self.var(argv[0]), SWITCH_TYPES[-1])
            self.write_line(f"EVT_CASE_FLAG({args_str})")
            self.indent += 1
        elif opcode == 0x20:
            self.indent -= 1
            self.write_line(f"EVT_END_CASE_GROUP")
            self.indent += 1
        elif opcode == 0x21:
            self.indent -= 1
            self.write_line(f"EVT_CASE_RANGE({self.var(argv[0])}, {self.var(argv[1])})")
            self.indent += 1
        elif opcode == 0x22:
            self.write_line(f"EVT_BREAK_SWITCH")
        elif opcode == 0x23:
            self.indent -= 2
            del SWITCH_TYPES[-1]
            self.write_line(f"EVT_END_SWITCH")
        elif opcode == 0x24:
            if self.var(argv[0]).startswith("LW"):
                new_arg = trim_lw(self.var(argv[0]))
                if self.var(argv[1]).startswith("LW"):
                    from_arg = trim_lw(self.var(argv[1]))  # Carry type info of LW if being set from an LW
                else:
                    from_arg = 0  # If a constant, we no longer know the type
                LOCAL_WORDS[int(new_arg)] = LOCAL_WORDS[int(from_arg)]

            self.write_line(f"EVT_SET({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x25:
            argNum = argv[1]
            sprite = (argNum & 0xFF0000) >> 16
            palette = (argNum & 0xFF00) >> 8
            anim = (argNum & 0xFF) >> 0
            if sprite > 0:
                value = make_anim_macro(self, sprite, palette, anim)
            elif argNum > 100:
                value = f"0x{argNum:08X}"
            else:
                value = f"{argNum}"
            self.write_line(f"EVT_SET_CONST({self.var(argv[0])}, {value})")
        elif opcode == 0x26:
            self.write_line(f"EVT_SETF({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x27:
            self.write_line(f"EVT_ADD({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x28:
            self.write_line(f"EVT_SUB({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x29:
            self.write_line(f"EVT_MUL({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x2A:
            self.write_line(f"EVT_DIV({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x2B:
            self.write_line(f"EVT_MOD({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x2C:
            self.write_line(f"EVT_ADDF({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x2D:
            self.write_line(f"EVT_SUBF({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x2E:
            self.write_line(f"EVT_MULF({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x2F:
            self.write_line(f"EVT_DIVF({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x30:
            self.write_line(f"EVT_USE_BUF({self.var(argv[0])})")
        elif opcode == 0x31 or opcode == 0x32 or opcode == 0x33 or opcode == 0x34:
            args = [*map(self.var, argv)]
            self.write_line(f"EVT_BUF_READ{opcode - 0x30}({', '.join(args)})")
        elif opcode == 0x35:
            args = [*map(self.var, argv)]
            self.write_line(f"EVT_BUF_PEEK({', '.join(args)})")
        elif opcode == 0x36:
            self.write_line(f"EVT_USE_FBUF({self.var(argv[0])})")
        elif opcode == 0x37 or opcode == 0x38 or opcode == 0x39 or opcode == 0x3A:
            args = [*map(self.var, argv)]
            self.write_line(f"EVT_FBUF_READ{opcode - 0x36}({', '.join(args)})")
        elif opcode == 0x3B:
            args = [*map(self.var, argv)]
            self.write_line(f"EVT_FBUF_PEEK({', '.join(args)})")
        elif opcode == 0x3C:
            self.write_line(f"EVT_USE_ARRAY({self.var(argv[0])})")
        elif opcode == 0x3D:
            self.write_line(f"EVT_USE_FLAG_ARRAY({self.var(argv[0])})")
        elif opcode == 0x3E:
            self.write_line(f"EVT_MALLOC_ARRAY({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x3F:
            self.write_line(f"EVT_BITWISE_AND({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x40:
            self.write_line(f"EVT_BITWISE_AND_CONST({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x41:
            self.write_line(f"EVT_BITWISE_OR({self.var(argv[0])}, 0x{argv[1]:X})")
        elif opcode == 0x42:
            self.write_line(f"EVT_BITWISE_OR_CONST({self.var(argv[0])}, 0x{argv[1]:X})")
        elif opcode == 0x43:
            func = self.addr_ref(argv[0])
            args = [self.var(a, use_evt_ptr=True) for a in argv[1:]]
            args_str = ", ".join(args)
            args_str = replace_constants(self, func, args_str)
            if func.startswith("evt_"):
                # use func-specific macro
                self.write_line(f"{func}({args_str})")
            # Since the map ascii for map transitions is global, and several of them share the same RAM address,
            # or ar not migrated, we have to create a placeholder
            elif func == "GotoMap" or func == "GotoMapSpecial":
                args = [self.var(a, use_evt_ptr=True) for a in argv[2:]]
                args_str = ", ".join(args)
                self.write_line(f"EVT_CALL({func}, EVT_PTR(UNK_STR_{argv[1]:X}), {args_str})")
            elif args_str:
                self.write_line(f"EVT_CALL({func}, {args_str})")
            else:
                self.write_line(f"EVT_CALL({func})")  # no args
        elif opcode == 0x44:
            self.write_line(f"EVT_EXEC({self.addr_ref(argv[0])})")
        elif opcode == 0x45:
            self.write_line(f"EVT_EXEC_GET_TID({self.addr_ref(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x46:
            self.write_line(f"EVT_EXEC_WAIT({self.addr_ref(argv[0])})")
        elif opcode == 0x47:
            args = [
                self.addr_ref(argv[0]),
                self.trigger(argv[1]),
                self.collider_id(argv[2]),
                *map(self.var, argv[3:]),
            ]
            self.write_line(f"EVT_BIND_TRIGGER({', '.join(args)})")
        elif opcode == 0x48:
            self.write_line(f"EVT_UNBIND")
        elif opcode == 0x49:
            self.write_line(f"EVT_KILL_THREAD({self.var(argv[0])})")
        elif opcode == 0x4A:
            self.write_line(f"EVT_JUMP({self.var(argv[0])})")
        elif opcode == 0x4B:
            self.write_line(f"EVT_SET_PRIORITY({self.var(argv[0])})")
        elif opcode == 0x4C:
            self.write_line(f"EVT_SET_TIMESCALE({self.var(argv[0])})")
        elif opcode == 0x4D:
            self.write_line(f"EVT_SET_GROUP({self.var(argv[0])})")
        elif opcode == 0x4E:
            args = [
                self.addr_ref(argv[0]),
                self.trigger(argv[1]),
                self.collider_id(argv[2]),
                *map(self.var, argv[3:]),
            ]
            self.write_line(f"EVT_BIND_PADLOCK({', '.join(args)})")
        elif opcode == 0x4F:
            self.write_line(f"EVT_SUSPEND_GROUP({self.var(argv[0])})")
        elif opcode == 0x50:
            self.write_line(f"EVT_RESUME_GROUP({self.var(argv[0])})")
        elif opcode == 0x51:
            self.write_line(f"EVT_SUSPEND_OTHERS({self.var(argv[0])})")
        elif opcode == 0x52:
            self.write_line(f"EVT_RESUME_OTHERS({self.var(argv[0])})")
        elif opcode == 0x53:
            self.write_line(f"EVT_SUSPEND_THREAD({self.var(argv[0])})")
        elif opcode == 0x54:
            self.write_line(f"EVT_RESUME_THREAD({self.var(argv[0])})")
        elif opcode == 0x55:
            self.write_line(f"EVT_IS_THREAD_RUNNING({self.var(argv[0])}, {self.var(argv[1])})")
        elif opcode == 0x56:
            self.write_line("EVT_THREAD")
            self.indent += 1
        elif opcode == 0x57:
            self.indent -= 1
            self.write_line("EVT_END_THREAD")
        elif opcode == 0x58:
            self.write_line("EVT_CHILD_THREAD")
            self.indent += 1
        elif opcode == 0x59:
            self.indent -= 1
            self.write_line("EVT_END_CHILD_THREAD")
        else:
            # unknown opcode
            argv_str = ""
            for arg in argv:
                argv_str += ", "
                argv_str += f"0x{arg:X}"
            self.write_line(f"0x{opcode:02X}{argv_str}),")

    def collider_id(self, arg):
        if arg >= 0x4000 and arg <= 0x5000:
            return f"EVT_ENTITY_INDEX({arg - 0x4000})"
        else:
            return self.var(arg)


class UnsupportedScript(Exception):
    pass


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument("file", type=str, help="File to dissassemble from")
    parser.add_argument("offset", help="Offset to start dissassembling from")
    parser.add_argument(
        "-end",
        "-e",
        "--e",
        type=lambda x: int(x, 16),
        default=0,
        dest="end",
        required=False,
        help="End offset to stop dissassembling from.\nOnly used as a way to find valid scripts.",
    )
    parser.add_argument(
        "-vram",
        "-v",
        "--v",
        type=lambda x: int(x, 16),
        default=0,
        dest="vram",
        required=False,
        help="VRAM start will be tracked and used for the script output name",
    )
    parser.add_argument(
        "-si",
        "--si",
        action="store_true",
        default=False,
        dest="si",
        required=False,
        help="Force si script output",
    )
    parser.add_argument(
        "-blob",
        "--b",
        action="store_true",
        default=False,
        dest="blob",
        required=False,
        help="If there is a blob of scripts.",
    )
    args = parser.parse_args()
    vram_base = args.vram
    get_constants()
    INCLUDED = {}
    INCLUDED["functions"] = set()
    INCLUDED["includes"] = set()
    INCLUDES_NEEDED = {}
    INCLUDES_NEEDED["include"] = []
    INCLUDES_NEEDED["forward"] = []
    INCLUDES_NEEDED["npcs"] = {}
    INCLUDES_NEEDED["sprites"] = set()
    try:
        offset = int(args.offset, 0)
    except ValueError:
        info = sym_info.search_symbol(args.offset)
        if info is None:
            print(f"{args.offset} is not a valid symbol name")
            exit(1)
        offset = info[0]
    if args.end > offset:
        # Search the given memory range and report scripts
        with open(args.file, "rb") as f:
            gap = False
            first_print = False
            while offset < args.end:
                f.seek(offset)
                script = ScriptDisassembler(f, args.offset, {}, 0x978DE0, INCLUDES_NEEDED, INCLUDED)
                try:
                    script_text = script.disassemble()
                    if script.instructions > 1 and "_EVT_CMD" not in script_text:
                        if gap and first_print:
                            potential_struct_sizes = {
                                "NpcData": 0x1F0,
                                "MobileAISettings": 0x30,
                                "NpcSettings": 0x2C,
                                "NpcGroupList": 0xC,
                            }
                            gap_size = offset - gap_start
                            potential_struct = "Unknown data"
                            potential_count = 1
                            for k, v in potential_struct_sizes.items():
                                if gap_size % v == 0:
                                    potential_struct = k
                                    potential_count = gap_size // v
                            print(
                                f"========== 0x{gap_size:X} byte gap ({potential_count} {potential_struct}?) 0x{gap_start:X} - 0x{offset:X} =========="
                            )
                            print()
                            gap = False
                        # print(f"EvtScript read from 0x{script.start_pos:X} to 0x{script.end_pos:X} "
                        #      f"(0x{script.end_pos - script.start_pos:X} bytes, {script.instructions} instructions)")
                        # print()
                        vram = f"{args.vram:X}_" if vram_base > 0 else f""
                        script_text = script_text.replace(
                            "EvtScript script = SCRIPT({",
                            f"EvtScript N(D_{vram}{offset:X}) = " + "SCRIPT({",
                        )
                        print(script_text, end="")
                        print()
                        # print(f"Valid script found at 0x{offset:X}")
                        args.vram += script.end_pos - offset
                        offset = script.end_pos
                        first_print = True
                    else:
                        if not gap:
                            gap_start = offset
                            gap = True
                        offset += 4
                        args.vram += 4
                except Exception:
                    if not gap:
                        gap_start = offset
                        gap = True
                    offset += 4
                    args.vram += 4
    else:
        with open(args.file, "rb") as f:
            f.seek(offset)
            loffset = args.offset
            looping = 1
            while looping:
                try:
                    script = ScriptDisassembler(f, loffset, {}, 0x978DE0, INCLUDES_NEEDED, INCLUDED)

                    if args.si:
                        print(
                            ScriptDisassembler(f, loffset, {}, 0x978DE0, INCLUDES_NEEDED, INCLUDED).disassemble(),
                            end="",
                        )
                    else:
                        try:
                            script_text = script.disassemble()

                            # print(f"EvtScript read from 0x{script.start_pos:X} to 0x{script.end_pos:X} "
                            #     f"(0x{script.end_pos - script.start_pos:X} bytes, {script.instructions} instructions)")
                            print()
                            print(script_text, end="")

                        except UnsupportedScript:
                            f.seek(offset)
                            print(ScriptDisassembler(f).disassemble(), end="")
                            break
                except Exception as e:
                    print(e)

                loffset = script.end_pos
                LOCAL_WORDS = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                looping = args.blob
                try:
                    loffset = _script_lib[loffset - info[0] + info[2]][0][1]
                except:
                    pass
