9 0xfd: ['delay', 'var'],
10 0xfc: ['call', 'addr'],
11 0xfb: ['jump', 'addr'],
12 0xfa: ['beqz', 'addr'],
13 0xf9: ['bltz', 'addr'],
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'],
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'],
37 0x00: ['testchdisabled', 'arg'],
38 0x50: ['subvariation', 'ign-arg'],
39 0x70: ['setvariation', 'ign-arg'],
40 0x80: ['getvariation', 'ign-arg'],
41 0x90: ['startchannel', 'arg', 'addr'],
48 0xfd: ['delay', 'var'],
49 0xfc: ['call', 'addr'],
50 0xfb: ['jump', 'addr'],
51 0xfa: ['beqz', 'addr'],
52 0xf9: ['bltz', 'addr'],
56 0xf5: ['bgez', 'addr'],
58 0xf2: ['reservenotes', 'u8'],
59 0xf1: ['unreservenotes'],
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'],
93 0x00: ['testlayerfinished', 'arg'],
94 0x10: ['startchannel', 'arg', 'addr'],
95 0x20: ['disablechannel', 'arg'],
96 0x30: ['iowriteval2', 'arg', 'u8'],
97 0x40: ['ioreadval2', 'arg', 'u8'],
98 0x50: ['ioreadvalsub', 'arg'],
99 0x60: ['setnotepriority', 'arg'],
100 0x70: ['iowriteval', 'arg'],
101 0x80: ['ioreadval', 'arg'],
102 0x90: ['setlayer', 'arg', 'addr'],
103 0xa0: ['freelayer', 'arg'],
104 0xb0: ['dynsetlayer', 'arg'],
107 commands_layer_base
= {
109 0xc0: ['delay', 'var'],
110 0xc1: ['setshortnotevelocity', 'u8'],
111 0xc2: ['transpose', 'u8'],
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'],
121 0xf8: ['loop', 'u8'],
122 0xfb: ['jump', 'addr'],
123 0xfc: ['call', 'addr'],
126 0xd0: ['setshortnotevelocityfromtable', 'arg'],
127 0xe0: ['setshortnotedurationfromtable', 'arg'],
130 commands
['layer_large'] = dict(list(commands_layer_base
.items()) + list({
131 0x00: ['note0', 'arg', 'var', 'u8', 'u8'],
132 0x40: ['note1', 'arg', 'var', 'u8'],
133 0x80: ['note2', 'arg', 'u8', 'u8'],
136 commands
['layer_small'] = dict(list(commands_layer_base
.items()) + list({
137 0x00: ['smallnote0', 'arg', 'var'],
138 0x40: ['smallnote1', 'arg'],
139 0x80: ['smallnote2', 'arg'],
142 print_end_padding
= False
143 if "--print-end-padding" in sys
.argv
:
144 print_end_padding
= True
145 sys
.argv
.remove("--print-end-padding")
147 if len(sys
.argv
) != 2:
148 print(f
"Usage: {sys.argv[0]} (--emit-asm-macros | input.m64)")
151 if sys
.argv
[1] == "--emit-asm-macros":
152 print("# Macros for disassembled sequence files. This file was automatically generated by seq_decoder.py.")
153 print("# To regenerate it, run: ./tools/seq_decoder.py --emit-asm-macros >seq_macros.inc")
156 print(f
" .byte {x} >> 8, {x} & 0xff")
158 def emit_cmd(key
, op
, cmd
):
163 nibble_param_name
= None
164 for i
, arg
in enumerate(args
):
165 param_name
= chr(97 + i
)
166 param_names
.append(param_name
)
167 param_list
.append(param_name
+ ("=0" if arg
== "ign-arg" else ""))
168 if arg
== "ign-arg" or arg
== "arg":
169 nibble_param_name
= param_name
170 print(f
".macro {key}_{mn} {', '.join(param_list)}".rstrip())
171 if nibble_param_name
is not None:
172 print(f
" .byte {hex(op)} + \\{nibble_param_name}")
174 print(f
" .byte {hex(op)}")
175 for arg
, param_name
in zip(args
, param_names
):
176 if arg
in ['arg', 'ign-arg']:
178 elif arg
in ['s8', 'u8', 'hex8']:
179 print(f
" .byte \\{param_name}")
180 elif arg
in ['u16', 'hex16']:
181 print_hword("\\" + param_name
)
183 print_hword(f
"(\\{param_name} - sequence_start)")
184 elif arg
== 'var_long':
185 print(f
" var_long \\{param_name}")
187 print(f
" var \\{param_name}")
189 raise Exception("Unknown argument type " + arg
)
193 def emit_env_cmd(op
, cmd
):
196 for i
, arg
in enumerate(cmd
[1:]):
197 param_list
.append(chr(97 + i
))
198 print(f
".macro envelope_{mn} {', '.join(param_list)}".rstrip())
200 print(f
" .byte {hex(op >> 8)}, {hex(op & 0xff)}")
201 for param
in param_list
:
202 print_hword("\\" + param
)
205 for key
in ['seq', 'chan', 'layer']:
206 print(f
"# {key} commands\n")
208 cmds
= commands
['layer_large']
209 for op
in sorted(commands
['layer_small'].keys()):
211 emit_cmd(key
, op
, commands
['layer_small'][op
])
216 for op
in sorted(cmds
.keys()):
218 if mn
== 'setnotepriority':
219 eu
.append((0xe9, ['setnotepriority', 'u8']))
220 non_eu
.append((op
, cmds
[op
]))
221 elif mn
in ['reservenotes', 'unreservenotes']:
222 eu
.append((op
- 1, cmds
[op
]))
223 non_eu
.append((op
, cmds
[op
]))
224 elif mn
not in ['portamento', 'writeseq']:
225 emit_cmd(key
, op
, cmds
[op
])
228 print(".macro chan_writeseq val, pos, offset")
229 print(" .byte 0xc7, \\val")
230 print_hword("(\\pos - sequence_start + \\offset)")
232 print(".macro chan_writeseq_nextinstr val, offset")
233 print(" .byte 0xc7, \\val")
234 print_hword("(writeseq\\@ - sequence_start + \\offset)")
235 print(" writeseq\\@:")
237 print(".macro layer_portamento a, b, c")
238 print(" .byte 0xc7, \\a, \\b")
239 print(" .if ((\\a & 0x80) == 0)")
245 emit_cmd(key
, 0xfd, ['delay_long', 'var_long'])
247 emit_cmd(key
, 0xc0, ['delay_long', 'var_long'])
248 emit_cmd(key
, 0x40, ['note1_long', 'arg', 'var_long', 'u8'])
250 print(".ifdef VERSION_EU\n")
252 emit_cmd(key
, op
, cmd
)
254 for (op
, cmd
) in non_eu
:
255 emit_cmd(key
, op
, cmd
)
258 print("# envelope commands\n")
259 emit_env_cmd(0, ['disable', 'u16'])
260 emit_env_cmd(2**16-1, ['hang', 'u16'])
261 emit_env_cmd(2**16-2, ['goto', 'u16'])
262 emit_env_cmd(2**16-3, ['restart', 'u16'])
263 emit_env_cmd(None, ['line', 'u16', 'u16'])
265 print("# other commands\n")
266 print(".macro var_long x")
267 print(" .byte (0x80 | (\\x & 0x7f00) >> 8), (\\x & 0xff)")
269 print(".macro var x")
270 print(" .if (\\x >= 0x80)")
271 print(" var_long \\x")
276 print(".macro sound_ref a")
277 print_hword("(\\a - sequence_start)")
281 filename
= sys
.argv
[1]
283 lang
= filename
.split('/')[-2]
284 assert lang
in ['us', 'jp', 'eu']
285 seq_num
= int(filename
.split('/')[-1].split('_')[0], 16)
291 with
open(filename
, 'rb') as f
:
294 print("Error: could not open file {filename} for reading.", file=sys
.stderr
)
297 output
= [None] * len(data
)
298 output_instate
= [None] * len(data
)
299 label_name
= [None] * len(data
)
300 script_start
= [False] * len(data
)
305 # Our analysis of large notes mode doesn't persist through multiple channel activations
306 # For simplicity, we force large notes always instead, which is valid for SM64.
307 force_large_notes
= True
310 # unreservenotes moved to 0xf0 in EU, and reservenotes took its place
311 commands
['chan'][0xf0] = commands
['chan'][0xf1]
312 commands
['chan'][0xf1] = commands
['chan'][0xf2]
313 del commands
['chan'][0xf2]
314 # total guess: the same is true for the 'seq'-type command
315 commands
['seq'][0xf0] = commands
['seq'][0xf1]
316 commands
['seq'][0xf1] = commands
['seq'][0xf2]
317 del commands
['seq'][0xf2]
318 # setnotepriority moved to 0xe9, becoming a non-arg command
319 commands
['chan'][0xe9] = ['setnotepriority', 'u8']
320 del commands
['chan'][0x60]
322 def is_arg_command(cmd_args
):
323 return 'arg' in cmd_args
or 'ign-arg' in cmd_args
325 def gen_label(ind
, tp
):
326 nice_tp
= tp
.replace('_small', '').replace('_large', '')
327 addr
= hex(ind
)[2:].upper()
328 ret
= f
".{nice_tp}_{addr}"
330 errors
.append(f
"reference to oob label {ret}")
333 if label_name
[ind
] is not None:
334 return label_name
[ind
]
335 label_name
[ind
] = ret
338 def gen_mnemonic(tp
, b
):
339 nice_tp
= tp
.split('_')[0]
340 mn
= commands
[tp
][b
][0]
343 return f
"{nice_tp}_{mn}"
347 def decode_one(state
):
348 pos
, tp
, nesting
, large
= state
356 if output
[pos
] is not None:
357 if output_instate
[pos
] != state
:
358 errors
.append(f
"got to {gen_label(orig_pos, tp)} with both state {state} and {output_instate[pos]}")
374 return (hi
<< 8) | lo
379 ret
= (ret
<< 8) & 0x7f00;
381 return (ret
, ret
< 0x80)
386 decode_list
.append((sound
, 'chan', 0, True))
387 if sound
< len(data
):
388 script_start
[sound
] = True
389 for p
in range(orig_pos
, pos
):
391 output_instate
[p
] = state
392 output
[orig_pos
] = 'sound_ref ' + gen_label(sound
, 'chan')
398 for p
in range(orig_pos
, pos
):
400 output_instate
[p
] = state
404 mn
= ['disable', 'hang', 'goto', 'restart'][-a
]
405 output
[orig_pos
] = f
'envelope_{mn} {b}'
406 # assume any goto is backwards and stop decoding
408 output
[orig_pos
] = f
'envelope_line {a} {b}'
409 decode_list
.append((pos
, tp
, nesting
, large
))
415 if ins_byte
in cmds
and not is_arg_command(cmds
[ins_byte
]):
418 elif ins_byte
& 0xf0 in cmds
and is_arg_command(cmds
[ins_byte
& 0xf0]):
419 used_b
= ins_byte
& 0xf0
421 elif ins_byte
& 0xc0 in cmds
and is_arg_command(cmds
[ins_byte
& 0xc0]) and tp
.startswith('layer'):
422 used_b
= ins_byte
& 0xc0
423 arg
= ins_byte
& 0x3f
425 errors
.append(f
"unrecognized instruction {hex(ins_byte)} for type {tp} at label {gen_label(orig_pos, tp)}")
428 out_mn
= gen_mnemonic(tp
, used_b
)
430 cmd_mn
= cmds
[used_b
][0]
431 cmd_args
= cmds
[used_b
][1:]
435 if cmd_mn
== 'portamento' and len(out_args
) == 2 and (int(out_args
[0], 0) & 0x80) == 0:
438 out_args
.append(str(arg
))
439 elif a
== 'ign-arg' and arg
!= 0:
440 out_args
.append(str(arg
))
442 out_args
.append(str(u8()))
444 out_args
.append(hex(u8()))
447 out_args
.append(str(v
if v
< 128 else v
- 256))
449 out_args
.append(str(u16()))
451 out_args
.append(hex(u16()))
454 out_args
.append(hex(val
))
463 elif cmd_mn
in ['jump', 'beqz', 'bltz', 'bgez']:
465 elif cmd_mn
== 'startchannel':
467 elif cmd_mn
== 'setlayer':
469 elif cmd_mn
== 'setdyntable':
471 elif cmd_mn
== 'setenvelope':
475 label
= gen_label(v
, kind
)
476 out_args
.append(label
)
477 errors
.append(f
"reference to oob label {label}")
478 elif cmd_mn
== 'writeseq':
479 out_args
.append('<fixup>')
480 seq_writes
.append((orig_pos
, v
))
482 out_args
.append(gen_label(v
, kind
))
484 decode_list
.append((v
, tp
, 0, large
))
485 script_start
[v
] = True
486 elif cmd_mn
in ['jump', 'beqz', 'bltz', 'bgez']:
487 decode_list
.append((v
, tp
, nesting
, large
))
488 elif cmd_mn
== 'startchannel':
489 decode_list
.append((v
, 'chan', 0, force_large_notes
))
490 script_start
[v
] = True
491 elif cmd_mn
== 'setlayer':
493 decode_list
.append((v
, 'layer_large', 0, True))
495 decode_list
.append((v
, 'layer_small', 0, True))
496 script_start
[v
] = True
497 elif cmd_mn
== 'setenvelope':
498 decode_list
.append((v
, 'envelope', 0, True))
499 script_start
[v
] = True
501 script_start
[v
] = True
508 out_all
+= ', '.join(out_args
)
509 for p
in range(orig_pos
, pos
):
511 output_instate
[p
] = state
512 output
[orig_pos
] = out_all
514 if cmd_mn
in ['hang', 'jump']:
516 if cmd_mn
in ['loop']:
520 if cmd_mn
in ['break', 'loopend']:
523 # This is iffy, and actually happens in sequence 0. It will make us
524 # return to the caller's caller at function end.
526 if cmd_mn
== 'largenoteson':
528 if cmd_mn
== 'largenotesoff':
531 decode_list
.append((pos
, tp
, nesting
, large
))
533 def decode_rec(state
, initial
):
536 gen_label(v
, state
[1])
537 script_start
[v
] = True
538 decode_list
.append(state
)
540 decode_one(decode_list
.pop())
543 decode_rec((0, 'seq', 0, False), initial
=True)
549 (0x8A8, 0x38), # stated as 0x30
550 (0xB66, 0x38), # stated as 0x30
552 (0x194B, 0x28), # stated as 0x20
556 # same script as bank 3
557 # same script as bank 5
560 (0x1FC4, 'layer_large'),
561 (0x2149, 'layer_large'),
562 (0x2223, 'layer_large'),
564 (0x3110, 'envelope'),
565 (0x31EC, 'envelope'),
570 (0x8F6, 0x38), # stated as 0x30
573 (0x1AF3, 0x28), # stated as 0x20
577 # same script as bank 3
578 # same script as bank 5
581 (0x216C, 'layer_large'),
582 (0x22F1, 'layer_large'),
583 (0x23CB, 'layer_large'),
585 (0x339C, 'envelope'),
586 (0x3478, 'envelope'),
591 (0x8FE, 0x38), # stated as 0x30?
594 (0x1B0C, 0x28), # stated as 0x20?
598 # same script as bank 3
599 # same script as bank 5
603 (0x2185, 'layer_large'),
604 (0x230A, 'layer_large'),
605 (0x23E4, 'layer_large'),
607 (0x33CC, 'envelope'),
608 (0x34A8, 'envelope'),
611 for (addr
, count
) in sound_banks
:
612 for i
in range(count
):
613 decode_rec((addr
+ 2*i
, 'soundref', 0, False), initial
=True)
615 for (addr
, tp
) in unused
:
616 gen_label(addr
, tp
+ '_unused')
617 decode_rec((addr
, tp
, 0, force_large_notes
), initial
=False)
619 for (pos
, write_to
) in seq_writes
:
620 assert '<fixup>' in output
[pos
]
622 while output
[write_to
] == '':
625 if write_to
> pos
and all(output
[i
] == '' for i
in range(pos
+1, write_to
)):
626 nice_target
= str(delta
)
627 output
[pos
] = output
[pos
].replace('writeseq', 'writeseq_nextinstr')
629 tp
= output_instate
[write_to
][1] if output_instate
[write_to
] is not None else 'addr'
630 nice_target
= gen_label(write_to
, tp
) + ", " + str(delta
)
631 output
[pos
] = output
[pos
].replace('<fixup>', nice_target
)
633 # Add unreachable 'end' markers
634 for i
in range(1, len(data
)):
635 if (data
[i
] == 0xff and output
[i
] is None and output
[i
- 1] is not None
636 and label_name
[i
] is None):
637 tp
= output_instate
[i
- 1][1]
638 if tp
in ["seq", "chan", "layer_small", "layer_large"]:
639 output
[i
] = gen_mnemonic(tp
, 0xff)
641 # Add envelope padding
642 for i
in range(1, len(data
) - 1):
643 if (data
[i
] == 0 and output
[i
] is None and output
[i
- 1] is not None and
644 output
[i
+ 1] is not None and label_name
[i
] is None and
645 output
[i
+ 1].startswith('envelope')):
646 script_start
[i
] = True
647 output
[i
] = "# padding\n.byte 0"
649 # Add 'unused' marker labels
650 for i
in range(1, len(data
)):
651 if (output
[i
] is None and output
[i
- 1] is not None and label_name
[i
] is None):
652 script_start
[i
] = True
653 gen_label(i
, 'unused')
655 # Remove up to 15 bytes of padding at the end
657 for i
in range(len(data
)-1, -1, -1):
658 if output
[i
] is not None:
664 if print_end_padding
:
668 print(".include \"seq_macros.inc\"")
669 print(".section .rodata")
671 print("sequence_start:")
673 for i
in range(len(data
) - end_padding
):
674 if script_start
[i
] and i
> 0:
676 if label_name
[i
] is not None:
677 print(f
"{label_name[i]}:")
680 print(f
".byte {hex(data[i])}")
683 elif label_name
[i
] is not None:
684 print("<mid-instruction>")
685 errors
.append(f
"mid-instruction label {label_name[i]}")
688 errors
.append("hit eof!?")
691 print(f
"[{filename}] errors:", file=sys
.stderr
)
693 print(w
, file=sys
.stderr
)