/
ff3msu.asm
527 lines (464 loc) · 12 KB
/
ff3msu.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
; Header stuff. Needed to put our routines in the right place in ROM.
.MEMORYMAP
SLOTSIZE $10000
DEFAULTSLOT 0
SLOT 0 $0000
.ENDME
.ROMBANKSIZE $10000
.ROMBANKS 48
.HIROM
.FASTROM
.BASE $C0 ; Needed to fix ExHiROM compat.
.BACKGROUND "ff3.sfc"
; Defines
.DEFINE PlayCommand $1300
.DEFINE PlayTrack $1301
.DEFINE PlayVolume $1302
.DEFINE CurrentCommand $1304
.DEFINE CurrentTrack $1305
.DEFINE CurrentVolume $1306
.DEFINE LastCommand $1308
.DEFINE LastTrack $1309
.DEFINE LastVolume $130A
.DEFINE OriginalNMIHandler $1500
.DEFINE ReturnCommand
; Was using $1E00-$1E07 earlier, but these are used for storing Veldt monsters. $1E20-$1E27 are unused.
; $7EF001 appears to be unused and is used so that the data for what track the MSU is currently playing
; can persist across saved games.
.DEFINE MSUExists $1E20
.DEFINE MSUCurrentTrack $1E21
.DEFINE MSUCurrentVolume $1E22
.DEFINE SPCCommandTemp $1E23
.DEFINE SPCVolumeTemp $1E24
.DEFINE DancingFlag $1E25
.DEFINE MSULastTrackSet $7EF001
; MSU Registers
.DEFINE MSUStatus $2000
.DEFINE MSUDRead $2001
.DEFINE MSUID $2002
.DEFINE MSUDSeek $2000
.DEFINE MSUTrack $2004
.DEFINE MSUVolume $2006
.DEFINE MSUControl $2007
; MSU Status Flags Definition
.DEFINE MSUStatus_DataBusy %10000000
.DEFINE MSUStatus_AudioBusy %01000000
.DEFINE MSUStatus_AudioLooping %00100000
.DEFINE MSUStatus_AudioPlaying %00010000
.DEFINE MSUStatus_BadTrack %00001000
; MSU Control Values Definition
.DEFINE MSUControl_Pause %00000100
.DEFINE MSUControl_PlayNoLoop %00000001
.DEFINE MSUControl_PlayLoop %00000011
.DEFINE MSUControl_Stop %00000000
; SPC Commands
.DEFINE SPCSubSong $82
.DEFINE SPCFade $81
.DEFINE SPCPlaySong $10
.DEFINE SPCInterrupt $14 ; TODO: Find out what this actually does. Is it a pause? A stop? Something else?
.DEFINE SPCSFX $18
; Subroutine hooks
.BANK 0
.ORG $ff10
.SECTION "NMIOverride" SIZE 4 OVERWRITE
jml NMIHandle
.ENDS
.BANK 5
.ORG $148
.SECTION "PlayCommand" SIZE 4 OVERWRITE
jml CommandHandle
.ENDS
.BANK 5
.ORG $182
.SECTION "TrackLoader" SIZE 4 OVERWRITE
jsl MSUMain
.ENDS
; Our free space to put our own stuff.
.BANK 18
.ORG $FA72
.SECTION "MSU" SIZE 1422 OVERWRITE
; Macros
; Input: Address of RAM with volume to change to (in SNES notation)
; Modifies: A
; No output.
.MACRO ChangeVolume
lda \1
bmi forcesub\@; If we're negative, since it's unsigned, we want to skip the comparisons and subtract.
cmp #$00 ; If we're being asked to silence, we don't want to change silence.
beq skip\@
cmp #$0A ; Don't subtract if volume is already less than 10
bcc skip\@
sbc #$40 ; Subtract 64
cmp #$0A ; If it's now less than 10, set it to 10.
bcs skip\@
lda #$0A
skip\@:
sta MSUCurrentVolume
sta MSUVolume
jmp done\@
forcesub\@:
sbc #$20
jmp skip\@
done\@:
.ENDM
; Input: Error character to indicate (uses FF3 character table)
; $80-$99 A-Z $9A-$B3 a-z $B4-BD 0-9
; Modifies: Terra's name in RAM to "ERROR(number)" (addresses $1602 through $1607 modified)
; Output: None
.MACRO SignalError
lda \1
sta $1607
lda #$84 ; E
sta $1602
lda #$91 ; R
sta $1603
sta $1604
sta $1606
lda #$8E ; O
sta $1605
.ENDM
; End Macros
; Main Code
CommandHandle:
; Check for specific commands
lda PlayCommand
cmp #SPCSubSong
bne +
jmp SubSongHandle
+
cmp #SPCFade
bne +
jmp FadeHandle
+
jmp OriginalCommand
SubSongHandle:
; Are we currently playing a Dancing Mad part?
lda MSULastTrackSet
cmp #$65
bne +
; The first time this is called during Dancing Mad Part 1 it seems to be a sort of 'false positive' at the start, and causes this code to skip part 1 entirely.
; so wait until it's called the second time.
lda DancingFlag
cmp #$01
bne setflag
jmp DancingMadPart2
+
cmp #$66
bne +
jmp DancingMadPart3
+
jmp OriginalCommand
setflag:
lda #$01
sta DancingFlag
jmp OriginalCommand
FadeHandle:
; We'll be doing a lot more here later, but for now, check for fades to volume 00, and silence the MSU-1 if found.
lda $1302
cmp #$00
bne +
stz MSUVolume
+
jmp OriginalCommand
DancingMadPart2:
; Play Part 2 of Dancing Mad
lda #$66
sta PlayTrack
lda #$ff
sta PlayVolume
jsl MSUMain
jmp OriginalCommand
DancingMadPart3:
; Play Part 3 of Dancing Mad
lda #$67
sta PlayTrack
lda #$ff
sta PlayVolume
jsl MSUMain
jmp OriginalCommand
OriginalCommand:
lda PlayCommand
beq +
jml $c5014c
+
jml $c50171
MSUMain:
; Has the MSU already been found? If so, skip this step
lda MSUExists
cmp #$01
beq MSUFound
; Check for MSU presence
jsr MSUCheck
cmp #$01
beq MSUFound
; If not found, do the original SPC code
jmp OriginalCode
MSUFound:
BattleCheck:
lda PlayTrack
; Special track handling section
cmp #$24 ; Battle theme
bne Kefka5Check
jml BattleTheme
Kefka5Check:
cmp #$50 ; Kefka's Dancing Mad Part 5
bne Kefka1Check
jml Kefka5
Kefka1Check:
cmp #$3b ; Kefka's Dancing Mad Parts 1-3
bne Ending1Check
lda #$65 ; Play part 1.
sta PlayTrack
Ending1Check: ; Ending Part 1
cmp #$53 ;
bne Ending2Check
jml Ending1
Ending2Check: ; Ending Part 2
cmp #$54 ;
bne SilenceCheck
jml Ending2
SilenceCheck:
cmp #$00 ; Silence (FF6 does a *lot* of track 0 requests, we're specifically masking this one to reduce calls to the MSU.)
bne RePlayCheck
jmp ShutUpAndLetMeTalk ; Mute, stop, and have the SPC handle the request for silence.
RePlayCheck:
cmp #$51 ; Trying to play silence. As far as I can tell, track 0x51 is not actually used by the game at any point, so this can be used
; as a signal to re-play our last track: If the code is calling for 0x51, what it's really calling for is the last track played.
bne SpecialHandlingBack
lda MSUCurrentTrack
sta PlayTrack
lda MSUCurrentVolume
sta PlayVolume
SpecialHandlingBack:
; Are we playing it?
lda PlayTrack
cmp MSULastTrackSet
; If not, skip to NotPlaying
bne NotPlaying
; Are we *really* playing it?
lda MSUStatus
and #MSUStatus_AudioPlaying
beq NotPlaying
; If so, is the volume the same?
lda PlayVolume
cmp MSUCurrentVolume
beq DoNothing
; If not, change our volume to match the new value.
; TODO: Fade to the new volume
ChangeVolume PlayVolume
DoNothing:
; Either way, silence the SPC volume and return
jmp SilenceAndReturn
NotPlaying:
; Okay, so we're not currently playing this track. Is the volume $00?
lda PlayVolume
cmp #$00
; If not, continue, if so, set our current volume to 00 and tell the MSU to stop, then do the SPC code.
bne ContinueToPlay
jmp ShutUpAndLetMeTalk
ContinueToPlay:
; Grab our track to play and push it at the MSU if it's not silence.
lda PlayTrack
cmp #$00
bne SetTrack
jmp ShutUpAndLetMeTalk
SetTrack:
; Fix for Sabin/Figaro bug: set $1309 to the actual last track set before changing LastTrackSet.
; fix for the fix: if MSULastTrackSet is the same as PlayTrack, don't clobber LastTrack
lda MSULastTrackSet
cmp PlayTrack
beq +
sta LastTrack
+
lda MSUCurrentVolume
sta LastVolume
lda PlayTrack
sta MSUTrack
; Write the last track set to an unused area of HiRAM, this should persist even after a load game.
sta MSULastTrackSet
stz MSUTrack+1
; Wait for the MSU to either be done loading or to return a track error
WaitMSU:
lda MSUStatus
and #MSUStatus_AudioBusy
bne WaitMSU
lda MSUStatus
and #MSUStatus_BadTrack
beq PlayMSU ; If it's not a bad track, don't jump to the SPC code
lda PlayTrack ; If it's a bad track and we're playing track 65, 66, or 67 (Dancing Mad tracks), play track 3b instead (original dancing mad trio)
cmp #$65
beq +
cmp #$66
beq +
cmp #$67
beq +
jmp ShutUpAndLetMeTalk
+
lda #$3b
sta PlayTrack
jmp ShutUpAndLetMeTalk
PlayMSU:
; Set the MSU Volume to the requested volume.
ChangeVolume PlayVolume
; Set our currently playing track to this one.
lda PlayTrack
sta MSUCurrentTrack
; Check against our looping track list. This subroutine will return the proper MSUControl value in A.
jsr WillItLoop
cmp #$00 ; If we're stopping, stop cleanly.
bne TimeToPlay
jmp ShutUp
TimeToPlay:
sta MSUControl
; We're now playing the track. Silence the SPC music and return
jmp SilenceAndReturn
; End Main Code
; Subroutines
; Check for MSU. If it's found, A and MSUExists will be $01
MSUCheck:
lda MSUID
cmp #$53 ; 'S'
bne + ; Stop checking if it's wrong
lda MSUID+1
cmp #$2D ; '-'
bne +
lda MSUID+2
cmp #$4D ; 'M'
bne +
lda MSUID+3
cmp #$53 ; 'S'
bne +
lda MSUID+4
cmp #$55 ; 'U'
bne +
lda MSUID+5
cmp #$31 ; '1'
bne +
lda #$01
sta MSUExists
rts
+
lda #$00
stz MSUExists
rts
; Check if the track to play is in our list of non-looping tracks, if so return $01 in A, if not, return $03 in A. Also, certain tracks essentially mean "stop", so handle those too.
WillItLoop:
lda PlayTrack
cmp #$00 ; Silence
beq WILStop
cmp #$02 ; Opening part 1
beq WILNope
cmp #$03 ; Opening part 2
beq WILNope
cmp #$04 ; Opening part 3
beq WILNope
cmp #$27 ; Aria de Mezzo Carattare
beq WILNope
cmp #$51 ; Silence
beq WILStop
cmp #$53 ; Ending part 1
beq WILNope
cmp #$54 ; Ending part 2
beq WILNope
lda #MSUControl_PlayLoop
rts
WILNope:
lda #MSUControl_PlayNoLoop
rts
WILStop:
lda #MSUControl_Stop
rts
; Battle and victory theme handling
BattleTheme:
lda MSUStatus ; Are we on Revision 2 or greater? If so, we have Resume support. Handle this specially.
and #%00000111
cmp #$02
bcs ResumeSupportBT
jml SpecialHandlingBack ; If not, do our normal stuff.
ResumeSupportBT:
lda #MSUControl_Pause ; Pause the current track.
sta MSUControl
jml SpecialHandlingBack
; Kefka 5 handling
Kefka5:
; If MSU-1 currently playing Dancing Mad 4 (which leads directly into 5 in our copy), then continue playing without modifying the MSU-1 state,
; otherwise process the track as normal.
lda MSUCurrentTrack
cmp #$52
bne +
lda MSUStatus
and #MSUStatus_AudioPlaying
beq +
jml SilenceAndReturn
+
jml SpecialHandlingBack
; Ending part 2 handling.
Ending2:
; We don't use this track, instead piggybacking onto the end of Ending Part 1, so just silence and return.
jml SilenceAndReturn
; Try to stop it from repeatedly restarting ending part 1
Ending1:
lda MSUCurrentTrack
cmp #$53
bne +
lda MSUStatus
and #MSUStatus_AudioPlaying
beq +
jml SilenceAndReturn
+
jml SpecialHandlingBack
; Return routines
ShutUp:
stz MSUVolume
stz MSUTrack
stz MSUTrack+1
stz MSUControl
SilenceAndReturn:
stz PlayVolume
; Skip silencing playtrack if we're currently playing problematic tracks (ones that rely on track timing)
lda PlayTrack
cmp #$27 ; Opera
beq OriginalCode
cmp #$45 ; Opera
beq OriginalCode
cmp #$53 ; Ending
beq OriginalCode
cmp #$54 ; Ending
beq OriginalCode
cmp #$38 ; Good Night jingle
beq OriginalCode
; Attempt to avoid the 'double play' problem by telling the SPC routine we're playing silence. May
; have unintended effects. May be cause of issues #4, partially #3, and #17. :/
lda #$51
sta PlayTrack
; End hack
OriginalCode:
lda PlayTrack
cmp CurrentTrack
rtl
ShutUpAndLetMeTalk:
stz MSUVolume
stz MSUTrack
stz MSUTrack+1
stz MSUControl
jmp OriginalCode
NMIHandle:
php
rep #$30
pha
phx
phy
phb
phd
sep #$20
;stuff goes here
rep #$30
pld
plb
ply
plx
pla
plp
jml OriginalNMIHandler
; End Subroutines
.ENDS