sm64

A Super Mario 64 decompilation
Log | Files | Refs | README | LICENSE

seq_decoder.py (24756B)


      1 #!/usr/bin/env python3
      2 import sys
      3 
      4 commands = {}
      5 commands['seq'] = {
      6     # non-arg commands
      7     0xff: ['end'],
      8     0xfe: ['delay1'],
      9     0xfd: ['delay', 'var'],
     10     0xfc: ['call', 'addr'],
     11     0xfb: ['jump', 'addr'],
     12     0xfa: ['beqz', 'addr'],
     13     0xf9: ['bltz', 'addr'],
     14     0xf8: ['loop', 'u8'],
     15     0xf7: ['loopend'],
     16     0xf5: ['bgez', 'addr'],
     17     0xf2: ['reservenotes', 'u8'],
     18     0xf1: ['unreservenotes'],
     19     0xdf: ['transpose', 's8'],
     20     0xde: ['transposerel', 's8'],
     21     0xdd: ['settempo', 'u8'],
     22     0xdc: ['addtempo', 's8'],
     23     0xdb: ['setvol', 'u8'],
     24     0xda: ['changevol', 's8'],
     25     0xd7: ['initchannels', 'hex16'],
     26     0xd6: ['disablechannels', 'hex16'],
     27     0xd5: ['setmutescale', 's8'],
     28     0xd4: ['mute'],
     29     0xd3: ['setmutebhv', 'hex8'],
     30     0xd2: ['setshortnotevelocitytable', 'addr'],
     31     0xd1: ['setshortnotedurationtable', 'addr'],
     32     0xd0: ['setnoteallocationpolicy', 'u8'],
     33     0xcc: ['setval', 'u8'],
     34     0xc9: ['bitand', 'u8'],
     35     0xc8: ['subtract', 'u8'],
     36     # arg commands
     37     0x00: ['testchdisabled', 'bits:4'],
     38     0x50: ['subvariation', 'bits:4:ign'],
     39     0x70: ['setvariation', 'bits:4:ign'],
     40     0x80: ['getvariation', 'bits:4:ign'],
     41     0x90: ['startchannel', 'bits:4', 'addr'],
     42 }
     43 
     44 commands['chan'] = {
     45     # non-arg commands
     46     0xff: ['end'],
     47     0xfe: ['delay1'],
     48     0xfd: ['delay', 'var'],
     49     0xfc: ['call', 'addr'],
     50     0xfb: ['jump', 'addr'],
     51     0xfa: ['beqz', 'addr'],
     52     0xf9: ['bltz', 'addr'],
     53     0xf8: ['loop', 'u8'],
     54     0xf7: ['loopend'],
     55     0xf6: ['break'],
     56     0xf5: ['bgez', 'addr'],
     57     0xf3: ['hang'],
     58     0xf2: ['reservenotes', 'u8'],
     59     0xf1: ['unreservenotes'],
     60     0xe4: ['dyncall'],
     61     0xe3: ['setvibratodelay', 'u8'],
     62     0xe2: ['setvibratoextentlinear', 'u8', 'u8', 'u8'],
     63     0xe1: ['setvibratoratelinear', 'u8', 'u8', 'u8'],
     64     0xe0: ['setvolscale', 'u8'],
     65     0xdf: ['setvol', 'u8'],
     66     0xde: ['freqscale', 'u16'],
     67     0xdd: ['setpan', 'u8'],
     68     0xdc: ['setpanmix', 'u8'],
     69     0xdb: ['transpose', 's8'],
     70     0xda: ['setenvelope', 'addr'],
     71     0xd9: ['setdecayrelease', 'u8'],
     72     0xd8: ['setvibratoextent', 'u8'],
     73     0xd7: ['setvibratorate', 'u8'],
     74     0xd6: ['setupdatesperframe_unimplemented', 'u8'],
     75     0xd4: ['setreverb', 'u8'],
     76     0xd3: ['pitchbend', 's8'],
     77     0xd2: ['setsustain', 'u8'],
     78     0xd1: ['setnoteallocationpolicy', 'u8'],
     79     0xd0: ['stereoheadseteffects', 'u8'],
     80     0xcc: ['setval', 'u8'],
     81     0xcb: ['readseq', 'addr'],
     82     0xca: ['setmutebhv', 'hex8'],
     83     0xc9: ['bitand', 'u8'],
     84     0xc8: ['subtract', 'u8'],
     85     0xc7: ['writeseq', 'u8', 'addr'],
     86     0xc6: ['setbank', 'u8'],
     87     0xc5: ['dynsetdyntable'],
     88     0xc4: ['largenoteson'],
     89     0xc3: ['largenotesoff'],
     90     0xc2: ['setdyntable', 'addr'],
     91     0xc1: ['setinstr', 'u8'],
     92     # arg commands
     93     0x00: ['testlayerfinished', 'bits:4'],
     94     0x10: ['startchannel', 'bits:4', 'addr'],
     95     0x20: ['disablechannel', 'bits:4'],
     96     0x30: ['iowriteval2', 'bits:4', 'u8'],
     97     0x40: ['ioreadval2', 'bits:4', 'u8'],
     98     0x50: ['ioreadvalsub', 'bits:4'],
     99     0x60: ['setnotepriority', 'bits:4'],
    100     0x70: ['iowriteval', 'bits:4'],
    101     0x80: ['ioreadval', 'bits:4'],
    102     0x90: ['setlayer', 'bits:4', 'addr'],
    103     0xa0: ['freelayer', 'bits:4'],
    104     0xb0: ['dynsetlayer', 'bits:4'],
    105 }
    106 
    107 commands_layer_base = {
    108     # non-arg commands
    109     0xc0: ['delay', 'var'],
    110     0xc1: ['setshortnotevelocity', 'u8'],
    111     0xc2: ['transpose', 's8'],
    112     0xc3: ['setshortnotedefaultplaypercentage', 'var'],
    113     0xc4: ['somethingon'], # ?? (something to do with decay behavior)
    114     0xc5: ['somethingoff'], # ??
    115     0xc6: ['setinstr', 'u8'],
    116     0xc7: ['portamento', 'hex8', 'u8', 'u8'],
    117     0xc8: ['disableportamento'],
    118     0xc9: ['setshortnoteduration', 'u8'],
    119     0xca: ['setpan', 'u8'],
    120     0xf7: ['loopend'],
    121     0xf8: ['loop', 'u8'],
    122     0xfb: ['jump', 'addr'],
    123     0xfc: ['call', 'addr'],
    124     0xff: ['end'],
    125     # arg commands
    126     0xd0: ['setshortnotevelocityfromtable', 'bits:4'],
    127     0xe0: ['setshortnotedurationfromtable', 'bits:4'],
    128 }
    129 
    130 commands['layer_large'] = dict(list(commands_layer_base.items()) + list({
    131     0x00: ['note0', 'bits:6', 'var', 'u8', 'u8'],
    132     0x40: ['note1', 'bits:6', 'var', 'u8'],
    133     0x80: ['note2', 'bits:6', 'u8', 'u8'],
    134 }.items()))
    135 
    136 commands['layer_small'] = dict(list(commands_layer_base.items()) + list({
    137     0x00: ['smallnote0', 'bits:6', 'var'],
    138     0x40: ['smallnote1', 'bits:6'],
    139     0x80: ['smallnote2', 'bits:6'],
    140 }.items()))
    141 
    142 sh_chan_overrides = [
    143     (0x80, ['testlayerfinished', 'bits:3']),
    144     (0x88, ['setlayer', 'bits:3', 'addr']),
    145     (0x60, ['ioreadval', 'bits:4']),
    146     (0x90, ['freelayer', 'bits:4']),
    147 ]
    148 
    149 def valid_cmd_for_nbits(cmd_list, nbits):
    150     for arg in cmd_list:
    151         if arg.startswith('bits:'):
    152             return int(arg.split(':')[1]) == nbits
    153     return nbits == 0
    154 
    155 print_end_padding = False
    156 if "--print-end-padding" in sys.argv:
    157     print_end_padding = True
    158     sys.argv.remove("--print-end-padding")
    159 
    160 if len(sys.argv) != 2:
    161     print(f"Usage: {sys.argv[0]} (--emit-asm-macros | input.m64)")
    162     sys.exit(0)
    163 
    164 if sys.argv[1] == "--emit-asm-macros":
    165     print("// Macros for disassembled sequence files. This file was automatically generated by seq_decoder.py.")
    166     print("// To regenerate it, run: ./tools/seq_decoder.py --emit-asm-macros > include/seq_macros.inc")
    167     print()
    168     def print_hword(x):
    169         print(f"    .byte {x} >> 8, {x} & 0xff")
    170 
    171     def emit_cmd(key, op, cmd):
    172         mn = cmd[0]
    173         args = cmd[1:]
    174         param_names = []
    175         param_list = []
    176         bits_param_name = None
    177         for i, arg in enumerate(args):
    178             param_name = chr(97 + i)
    179             param_names.append(param_name)
    180             param_list.append(param_name + ("=0" if arg.endswith(":ign") else ""))
    181             if arg.startswith("bits:"):
    182                 bits_param_name = param_name
    183         print(f".macro {key}_{mn} {', '.join(param_list)}".rstrip())
    184         if bits_param_name is not None:
    185             print(f"    .byte {hex(op)} + \\{bits_param_name}")
    186         else:
    187             print(f"    .byte {hex(op)}")
    188         for arg, param_name in zip(args, param_names):
    189             if arg.startswith('bits:'):
    190                 pass
    191             elif arg in ['s8', 'u8', 'hex8']:
    192                 print(f"    .byte \\{param_name}")
    193             elif arg in ['u16', 'hex16']:
    194                 print_hword("\\" + param_name)
    195             elif arg == 'addr':
    196                 print_hword(f"(\\{param_name} - sequence_start)")
    197             elif arg == 'var_long':
    198                 print(f"    var_long \\{param_name}")
    199             elif arg == 'var':
    200                 print(f"    var \\{param_name}")
    201             else:
    202                 raise Exception("Unknown argument type " + arg)
    203         print(".endm")
    204         print()
    205 
    206     def emit_env_cmd(op, cmd):
    207         mn = cmd[0]
    208         param_names = []
    209         param_list = []
    210         for i, arg in enumerate(cmd[1:]):
    211             param_name = chr(97 + i)
    212             param_names.append(param_name)
    213             param_list.append(param_name + ("=0" if arg.endswith(":ign") else ""))
    214         print(f".macro envelope_{mn} {', '.join(param_list)}".rstrip())
    215         if op is not None:
    216             print(f"    .byte {hex(op >> 8)}, {hex(op & 0xff)}")
    217         for param in param_names:
    218             print_hword("\\" + param)
    219         print(".endm\n")
    220 
    221     for key in ['seq', 'chan', 'layer']:
    222         print(f"// {key} commands\n")
    223         if key == 'layer':
    224             cmds = commands['layer_large']
    225             for op in sorted(commands['layer_small'].keys()):
    226                 if op not in cmds:
    227                     emit_cmd(key, op, commands['layer_small'][op])
    228         else:
    229             cmds = commands[key]
    230         eu_sh = []
    231         us_jp = []
    232         sh = sh_chan_overrides if key == 'chan' else []
    233         non_sh = []
    234         for op in sorted(cmds.keys()):
    235             mn = cmds[op][0]
    236             if mn == 'setnotepriority':
    237                 eu_sh.append((0xe9, ['setnotepriority', 'u8']))
    238                 us_jp.append((op, cmds[op]))
    239             elif mn in ['reservenotes', 'unreservenotes']:
    240                 eu_sh.append((op - 1, cmds[op]))
    241                 us_jp.append((op, cmds[op]))
    242             elif mn in ['testlayerfinished', 'setlayer', 'ioreadval', 'freelayer']:
    243                 non_sh.append((op, cmds[op]))
    244             elif mn not in ['portamento', 'writeseq']:
    245                 emit_cmd(key, op, cmds[op])
    246 
    247         if key == 'chan':
    248             print(".macro chan_writeseq val, pos, offset")
    249             print("    .byte 0xc7, \\val")
    250             print_hword("(\\pos - sequence_start + \\offset)")
    251             print(".endm\n")
    252             print(".macro chan_writeseq_nextinstr val, offset")
    253             print("    .byte 0xc7, \\val")
    254             print_hword("(writeseq\\@ - sequence_start + \\offset)")
    255             print("    writeseq\\@:")
    256             print(".endm\n")
    257             print(".macro layer_portamento a, b, c")
    258             print("    .byte 0xc7, \\a, \\b")
    259             print("    .if ((\\a & 0x80) == 0)")
    260             print("        var \\c")
    261             print("    .else")
    262             print("        .byte \\c")
    263             print("    .endif")
    264             print(".endm\n")
    265             emit_cmd(key, 0xfd, ['delay_long', 'var_long'])
    266         if key == 'layer':
    267             emit_cmd(key, 0xc0, ['delay_long', 'var_long'])
    268             emit_cmd(key, 0x40, ['note1_long', 'bits:4', 'var_long', 'u8'])
    269         if eu_sh or us_jp or sh or non_sh:
    270             print("#if defined(VERSION_SH) || defined(VERSION_CN)\n")
    271             for (op, cmd) in eu_sh:
    272                 emit_cmd(key, op, cmd)
    273             for (op, cmd) in sh:
    274                 emit_cmd(key, op, cmd)
    275             print("#else\n")
    276             for (op, cmd) in non_sh:
    277                 emit_cmd(key, op, cmd)
    278             print("#ifdef VERSION_EU\n")
    279             for (op, cmd) in eu_sh:
    280                 emit_cmd(key, op, cmd)
    281             print("#else\n")
    282             for (op, cmd) in us_jp:
    283                 emit_cmd(key, op, cmd)
    284             print("#endif\n")
    285             print("#endif\n")
    286 
    287     print("// envelope commands\n")
    288     emit_env_cmd(0, ['disable', 'u16'])
    289     emit_env_cmd(2**16-1, ['hang', 'u16:ign'])
    290     emit_env_cmd(2**16-2, ['goto', 'u16'])
    291     emit_env_cmd(2**16-3, ['restart', 'u16:ign'])
    292     emit_env_cmd(None, ['line', 'u16', 'u16'])
    293 
    294     print("// other commands\n")
    295     print(".macro var_long x")
    296     print("     .byte (0x80 | (\\x & 0x7f00) >> 8), (\\x & 0xff)")
    297     print(".endm\n")
    298     print(".macro var x")
    299     print("    .if (\\x >= 0x80)")
    300     print("        var_long \\x")
    301     print("    .else")
    302     print("        .byte \\x")
    303     print("    .endif")
    304     print(".endm\n")
    305     print(".macro sound_ref a")
    306     print_hword("(\\a - sequence_start)")
    307     print(".endm")
    308     sys.exit(0)
    309 
    310 filename = sys.argv[1]
    311 try:
    312     lang = filename.split('/')[-2]
    313     assert lang in ['us', 'jp', 'eu', 'sh']
    314     seq_num = int(filename.split('/')[-1].split('_')[0], 16)
    315 except Exception:
    316     lang = ''
    317     seq_num = -1
    318 
    319 try:
    320     with open(filename, 'rb') as f:
    321         data = f.read()
    322 except Exception:
    323     print("Error: could not open file {filename} for reading.", file=sys.stderr)
    324     sys.exit(1)
    325 
    326 output = [None] * len(data)
    327 output_instate = [None] * len(data)
    328 label_name = [None] * len(data)
    329 script_start = [False] * len(data)
    330 hit_eof = False
    331 errors = []
    332 seq_writes = []
    333 
    334 # Our analysis of large notes mode doesn't persist through multiple channel activations
    335 # For simplicity, we force large notes always instead, which is valid for SM64.
    336 force_large_notes = True
    337 
    338 if lang in ['eu', 'sh']:
    339     # unreservenotes moved to 0xf0 in EU, and reservenotes took its place
    340     commands['chan'][0xf0] = commands['chan'][0xf1]
    341     commands['chan'][0xf1] = commands['chan'][0xf2]
    342     del commands['chan'][0xf2]
    343     # total guess: the same is true for the 'seq'-type command
    344     commands['seq'][0xf0] = commands['seq'][0xf1]
    345     commands['seq'][0xf1] = commands['seq'][0xf2]
    346     del commands['seq'][0xf2]
    347     # setnotepriority moved to 0xe9, becoming a non-arg command
    348     commands['chan'][0xe9] = ['setnotepriority', 'u8']
    349     del commands['chan'][0x60]
    350 
    351 if lang == 'sh':
    352     del commands['chan'][0x00]
    353     del commands['chan'][0xa0]
    354     for op, cmd_list in sh_chan_overrides:
    355         commands['chan'][op] = cmd_list
    356 
    357 def gen_label(ind, tp):
    358     nice_tp = tp.replace('_small', '').replace('_large', '')
    359     addr = hex(ind)[2:].upper()
    360     ret = f".{nice_tp}_{addr}"
    361     if ind >= len(data):
    362         errors.append(f"reference to oob label {ret}")
    363         return ret
    364 
    365     if label_name[ind] is not None:
    366         return label_name[ind]
    367     label_name[ind] = ret
    368     return ret
    369 
    370 def gen_mnemonic(tp, b):
    371     nice_tp = tp.split('_')[0]
    372     mn = commands[tp][b][0]
    373     if not mn:
    374         mn = f"{b:02X}"
    375     return f"{nice_tp}_{mn}"
    376 
    377 decode_list = []
    378 
    379 def decode_one(state):
    380     pos, tp, nesting, large = state
    381     orig_pos = pos
    382 
    383     if pos >= len(data):
    384         global hit_eof
    385         hit_eof = True
    386         return
    387 
    388     if output[pos] is not None:
    389         if output_instate[pos] != state:
    390             errors.append(f"got to {gen_label(orig_pos, tp)} with both state {state} and {output_instate[pos]}")
    391         return
    392 
    393     def u8():
    394         nonlocal pos
    395         global hit_eof
    396         if pos == len(data):
    397             hit_eof = True
    398             return 0
    399         ret = data[pos]
    400         pos += 1
    401         return ret
    402 
    403     def u16():
    404         hi = u8()
    405         lo = u8()
    406         return (hi << 8) | lo
    407 
    408     def var():
    409         ret = u8()
    410         if ret & 0x80:
    411             ret = (ret << 8) & 0x7f00;
    412             ret |= u8()
    413             return (ret, ret < 0x80)
    414         return (ret, False)
    415 
    416     if tp == 'soundref':
    417         sound = u16()
    418         decode_list.append((sound, 'chan', 0, True))
    419         if sound < len(data):
    420             script_start[sound] = True
    421         for p in range(orig_pos, pos):
    422             output[p] = ''
    423             output_instate[p] = state
    424         output[orig_pos] = 'sound_ref ' + gen_label(sound, 'chan')
    425         return
    426 
    427     if tp == 'envelope':
    428         a = u16()
    429         b = u16()
    430         for p in range(orig_pos, pos):
    431             output[p] = ''
    432             output_instate[p] = state
    433         if a >= 2**16 - 3:
    434             a -= 2**16
    435         if a <= 0:
    436             mn = ['disable', 'hang', 'goto', 'restart'][-a]
    437             output[orig_pos] = f'envelope_{mn} {b}'
    438             # assume any goto is backwards and stop decoding
    439         else:
    440             output[orig_pos] = f'envelope_line {a} {b}'
    441             decode_list.append((pos, tp, nesting, large))
    442         return
    443 
    444     ins_byte = u8()
    445 
    446     cmds = commands[tp]
    447     nbits = 0
    448     used_b = ins_byte
    449     while True:
    450         if used_b in cmds and valid_cmd_for_nbits(cmds[used_b], nbits):
    451             break
    452         used_b &= ~(1 << nbits)
    453         nbits += 1
    454         if nbits == 8:
    455             errors.append(f"unrecognized instruction {hex(ins_byte)} for type {tp} at label {gen_label(orig_pos, tp)}")
    456             return
    457 
    458     out_mn = gen_mnemonic(tp, used_b)
    459     out_args = []
    460     cmd_mn = cmds[used_b][0]
    461     cmd_args = cmds[used_b][1:]
    462     long_var = False
    463 
    464     for a in cmd_args:
    465         if cmd_mn == 'portamento' and len(out_args) == 2 and (int(out_args[0], 0) & 0x80) == 0:
    466             a = 'var'
    467         if a.startswith('bits:'):
    468             low_bits = ins_byte & ((1 << nbits) - 1)
    469             if not (a.endswith(':ign') and low_bits == 0):
    470                 out_args.append(str(low_bits))
    471         elif a == 'u8':
    472             out_args.append(str(u8()))
    473         elif a == 'hex8':
    474             out_args.append(hex(u8()))
    475         elif a == 's8':
    476             v = u8()
    477             out_args.append(str(v if v < 128 else v - 256))
    478         elif a == 'u16':
    479             out_args.append(str(u16()))
    480         elif a == 'hex16':
    481             out_args.append(hex(u16()))
    482         elif a == 'var':
    483             val, bad = var()
    484             out_args.append(hex(val))
    485             if bad:
    486                 long_var = True
    487         elif a == 'addr':
    488             v = u16()
    489 
    490             kind = 'addr'
    491             if cmd_mn == 'call':
    492                 kind = tp + '_fn'
    493             elif cmd_mn in ['jump', 'beqz', 'bltz', 'bgez']:
    494                 kind = tp
    495             elif cmd_mn == 'startchannel':
    496                 kind = 'chan'
    497             elif cmd_mn == 'setlayer':
    498                 kind = 'layer'
    499             elif cmd_mn == 'setdyntable':
    500                 kind = 'table'
    501             elif cmd_mn == 'setenvelope':
    502                 kind = 'envelope'
    503 
    504             if v >= len(data):
    505                 label = gen_label(v, kind)
    506                 out_args.append(label)
    507                 errors.append(f"reference to oob label {label}")
    508             elif cmd_mn == 'writeseq':
    509                 out_args.append('<fixup>')
    510                 seq_writes.append((orig_pos, v))
    511             else:
    512                 out_args.append(gen_label(v, kind))
    513                 if cmd_mn == 'call':
    514                     decode_list.append((v, tp, 0, large))
    515                     script_start[v] = True
    516                 elif cmd_mn in ['jump', 'beqz', 'bltz', 'bgez']:
    517                     decode_list.append((v, tp, nesting, large))
    518                 elif cmd_mn == 'startchannel':
    519                     decode_list.append((v, 'chan', 0, force_large_notes))
    520                     script_start[v] = True
    521                 elif cmd_mn == 'setlayer':
    522                     if large:
    523                         decode_list.append((v, 'layer_large', 0, True))
    524                     else:
    525                         decode_list.append((v, 'layer_small', 0, True))
    526                     script_start[v] = True
    527                 elif cmd_mn == 'setenvelope':
    528                     decode_list.append((v, 'envelope', 0, True))
    529                     script_start[v] = True
    530                 else:
    531                     script_start[v] = True
    532 
    533     out_all = out_mn
    534     if long_var:
    535         out_all += "_long"
    536     if out_args:
    537         out_all += ' '
    538         out_all += ', '.join(out_args)
    539     for p in range(orig_pos, pos):
    540         output[p] = ''
    541         output_instate[p] = state
    542     output[orig_pos] = out_all
    543 
    544     if cmd_mn in ['hang', 'jump']:
    545         return
    546     if cmd_mn in ['loop']:
    547         nesting += 1
    548     if cmd_mn == 'end':
    549         nesting -= 1
    550     if cmd_mn in ['break', 'loopend']:
    551         nesting -= 1
    552         if nesting < 0:
    553             # This is iffy, and actually happens in sequence 0. It will make us
    554             # return to the caller's caller at function end.
    555             nesting = 0
    556     if cmd_mn == 'largenoteson':
    557         large = True
    558     if cmd_mn == 'largenotesoff':
    559         large = False
    560     if nesting >= 0:
    561         decode_list.append((pos, tp, nesting, large))
    562 
    563 def decode_rec(state, initial):
    564     if not initial:
    565         v = state[0]
    566         gen_label(v, state[1])
    567         script_start[v] = True
    568     decode_list.append(state)
    569     while decode_list:
    570         decode_one(decode_list.pop())
    571 
    572 def main():
    573     decode_rec((0, 'seq', 0, False), initial=True)
    574 
    575     if seq_num == 0:
    576         if lang == 'jp':
    577             sound_banks = [
    578                 (0x14C, 0x70),
    579                 (0x8A8, 0x38), # stated as 0x30
    580                 (0xB66, 0x38), # stated as 0x30
    581                 (0xE09, 0x80),
    582                 (0x194B, 0x28), # stated as 0x20
    583                 (0x1CA6, 0x80),
    584                 (0x27C9, 0x20),
    585                 (0x2975, 0x30),
    586                 # same script as bank 3
    587                 # same script as bank 5
    588             ]
    589             unused = [
    590                 (0x1FC4, 'layer_large'),
    591                 (0x2149, 'layer_large'),
    592                 (0x2223, 'layer_large'),
    593                 (0x28C5, 'chan'),
    594                 (0x3110, 'envelope'),
    595                 (0x31EC, 'envelope'),
    596             ]
    597         elif lang == 'us':
    598             sound_banks = [
    599                 (0x14C, 0x70),
    600                 (0x8F6, 0x38), # stated as 0x30
    601                 (0xBB4, 0x40),
    602                 (0xF8E, 0x80),
    603                 (0x1AF3, 0x28), # stated as 0x20
    604                 (0x1E4E, 0x80),
    605                 (0x2971, 0x20),
    606                 (0x2B1D, 0x40),
    607                 # same script as bank 3
    608                 # same script as bank 5
    609             ]
    610             unused = [
    611                 (0x216C, 'layer_large'),
    612                 (0x22F1, 'layer_large'),
    613                 (0x23CB, 'layer_large'),
    614                 (0x2A6D, 'chan'),
    615                 (0x339C, 'envelope'),
    616                 (0x3478, 'envelope'),
    617             ]
    618         elif lang == 'eu':
    619             sound_banks = [
    620                 (0x154, 0x70),
    621                 (0x8FE, 0x38), # stated as 0x30
    622                 (0xBBC, 0x40),
    623                 (0xFA5, 0x80),
    624                 (0x1B0C, 0x28), # stated as 0x20
    625                 (0x1E67, 0x80),
    626                 (0x298A, 0x20),
    627                 (0x2B36, 0x40),
    628                 # same script as bank 3
    629                 # same script as bank 5
    630             ]
    631             unused = [
    632                 (0xF9A, 'chan'),
    633                 (0x2185, 'layer_large'),
    634                 (0x230A, 'layer_large'),
    635                 (0x23E4, 'layer_large'),
    636                 (0x2A86, 'chan'),
    637                 (0x33CC, 'envelope'),
    638                 (0x34A8, 'envelope'),
    639             ]
    640         elif lang == 'sh':
    641             sound_banks = [
    642                 (0x154, 0x70),
    643                 (0x8FE, 0x38), # stated as 0x30
    644                 (0xBBC, 0x40),
    645                 (0xFA5, 0x80),
    646                 (0x1B11, 0x28), # stated as 0x20
    647                 (0x1E6C, 0x80),
    648                 (0x298F, 0x20),
    649                 (0x2B3B, 0x40),
    650                 # same script as bank 3
    651                 # same script as bank 5
    652             ]
    653 
    654             unused = [
    655                 (0xF9A, 'chan'),
    656                 (0x218A, 'layer_large'),
    657                 (0x230F, 'layer_large'),
    658                 (0x23E9, 'layer_large'),
    659                 (0x2A8B, 'chan'),
    660                 (0x33D0, 'envelope'),
    661                 (0x34AC, 'envelope'),
    662             ]
    663 
    664 
    665         for (addr, count) in sound_banks:
    666             for i in range(count):
    667                 decode_rec((addr + 2*i, 'soundref', 0, False), initial=True)
    668 
    669         for (addr, tp) in unused:
    670             gen_label(addr, tp + '_unused')
    671             decode_rec((addr, tp, 0, force_large_notes), initial=False)
    672 
    673     for (pos, write_to) in seq_writes:
    674         assert '<fixup>' in output[pos]
    675         delta = 0
    676         while output[write_to] == '':
    677             write_to -= 1
    678             delta += 1
    679         if write_to > pos and all(output[i] == '' for i in range(pos+1, write_to)):
    680             nice_target = str(delta)
    681             output[pos] = output[pos].replace('writeseq', 'writeseq_nextinstr')
    682         else:
    683             tp = output_instate[write_to][1] if output_instate[write_to] is not None else 'addr'
    684             nice_target = gen_label(write_to, tp) + ", " + str(delta)
    685         output[pos] = output[pos].replace('<fixup>', nice_target)
    686 
    687     # Add unreachable 'end' markers
    688     for i in range(1, len(data)):
    689         if (data[i] == 0xff and output[i] is None and output[i - 1] is not None
    690                 and label_name[i] is None):
    691             tp = output_instate[i - 1][1]
    692             if tp in ["seq", "chan", "layer_small", "layer_large"]:
    693                 output[i] = gen_mnemonic(tp, 0xff)
    694 
    695     # Add envelope padding
    696     for i in range(1, len(data) - 1):
    697         if (data[i] == 0 and output[i] is None and output[i - 1] is not None and
    698                 output[i + 1] is not None and label_name[i] is None and
    699                 output[i + 1].startswith('envelope')):
    700             script_start[i] = True
    701             output[i] = "# padding\n.byte 0"
    702 
    703     # Add 'unused' marker labels
    704     for i in range(1, len(data)):
    705         if (output[i] is None and output[i - 1] is not None and label_name[i] is None):
    706             script_start[i] = True
    707             gen_label(i, 'unused')
    708 
    709     # Remove up to 15 bytes of padding at the end
    710     end_padding = 0
    711     for i in range(len(data)-1, -1, -1):
    712         if output[i] is not None:
    713             break
    714         end_padding += 1
    715     if end_padding > 15:
    716         end_padding = 0
    717 
    718     if print_end_padding:
    719         print(end_padding)
    720         sys.exit(0)
    721 
    722     print(".include \"seq_macros.inc\"")
    723     print(".section .rodata")
    724     print(".align 0")
    725     print("sequence_start:")
    726     print()
    727     for i in range(len(data) - end_padding):
    728         if script_start[i] and i > 0:
    729             print()
    730         if label_name[i] is not None:
    731             print(f"{label_name[i]}:")
    732         o = output[i]
    733         if o is None:
    734             print(f".byte {hex(data[i])}")
    735         elif o:
    736             print(o)
    737         elif label_name[i] is not None:
    738             print("<mid-instruction>")
    739             errors.append(f"mid-instruction label {label_name[i]}")
    740 
    741     if hit_eof:
    742         errors.append("hit eof!?")
    743 
    744     if errors:
    745         print(f"[{filename}] errors:", file=sys.stderr)
    746         for w in errors:
    747             print(w, file=sys.stderr)
    748 
    749 main()