#!/usr/bin/python3

import os
import sys
import struct
import argparse
import re
from overlayhelpers.filemap import GetFromVRam

ICHAIN_MACROS = [
    'ICHAIN_U8',
    'ICHAIN_S8',
    'ICHAIN_U16',
    'ICHAIN_S16',
    'ICHAIN_U32',
    'ICHAIN_S32',
    'ICHAIN_F32',
    'ICHAIN_F32_DIV1000',
    'ICHAIN_VEC3F',
    'ICHAIN_VEC3F_DIV1000',
    'ICHAIN_VEC3S',
]

Z64_ACTOR_PATH = "../include/z64actor.h"

def get_rom_address(offset):

    if offset.startswith("D_"):
        offset = offset[2:]

    offset = int(offset, 16)

    if offset >= 0x80000000:
        result = GetFromVRam(offset)
        offset = result.vrom.start + result.offset

    return hex(offset)

def get_actor_var_names():
    in_actor = False
    actor_vars = {}
    with open(os.path.dirname(os.path.realpath(__file__)) + "/" + Z64_ACTOR_PATH) as actor_h:
        for line in actor_h:
            if in_actor:
                if "}" in line:
                    # Reached the end of the actor struct so break out
                    break

                # Parse out the memory address (from the comment) and the variable name
                regex = r'.*\/\* (.*) \*\/\s+(struct)?\s*.+\s+(.+);.*'
                actor_var_info = re.match(regex, line)

                if actor_var_info:
                    # Strip off the 0x0* part and store it
                    new_var_index = re.sub('0x0*', '', actor_var_info[1])
                    actor_vars[new_var_index] = actor_var_info[3]

            elif "typedef struct Actor {" in line:
                # Found the Actor struct
                in_actor = True
    return actor_vars

def main():
    parser = argparse.ArgumentParser(description='Decompiles an InitChain')
    parser.add_argument('filename', help='ROM file path')
    parser.add_argument('offset', help='ROM offset or symbol of an InitChain')
    parser.add_argument('--names', action="store_true", help='Retrieve variable names from the actor struct')
    args = parser.parse_args()

    # Get the ROM address, if the offset is already a ROM address it will just be returned.
    args.offset = get_rom_address(args.offset)

    romOff = int(args.offset, 16)

    try:
        with open(args.filename, 'rb') as f:
            romData = f.read()
    except IOError:
        print('failed to read file ' + args.filename)
        sys.exit(1)

    print ('static InitChainEntry sInitChain[] = {')

    if args.names:
        actor_variable_names = get_actor_var_names()

    while True:
        entry = struct.unpack('>I', romData[romOff:romOff+4])[0]
        romOff += 4

        cont = entry >> 31
        t = (entry >> 27) & 0xF
        offset = ((entry) >> 16) & 0x7FF
        value = (entry) & 0xFFFF
        if value >= 0x8000 and not ICHAIN_MACROS[t].startswith('ICHAIN_U'):
            value -= 0x10000

        var_name = '{0:X}'.format(offset)

        if args.names and var_name in actor_variable_names:
            var_name = actor_variable_names[var_name]
        else:
            var_name = "unk_" + var_name

        print('    {0}({1}, {2}, {3}),'.format(ICHAIN_MACROS[t], var_name, value, ('ICHAIN_CONTINUE' if cont == 1 else 'ICHAIN_STOP')))
        if cont == 0:
            break
    print ('};')

if __name__ == "__main__":
    main()
