[SNES] Final Fantasy VI - Need help with a map's default song change

Started by edale, December 11, 2017, 02:22:45 AM

Previous topic - Next topic

edale

Hi all, I've been doing a lot of the (more recent) audio work for the Final Fantasy VI Dancing Mad MSU-1 mod (https://github.com/Insidious611/DancingMadFF6/releases), and in my researching songs and albums to convert, I stumbled accross the FFVI Special Tracks album.

What the Special Tracks album is, is several songs that were written for FFVI, but not included in the game. Among the tracks are 2 town theme songs, titled Town 2 (https://www.youtube.com/watch?v=j7MDMH7SUEQ) and Town 3 (https://www.youtube.com/watch?v=0S0J5JilEOk).

Thanks to the MSU-1 mod, it's now actually possible to add these songs back into the game! The problem is, to do that as-is, you'd have to replace 2 of the existing town themes to add them in.

Now here's where the fun starts.

I decided to try and mod the game, to add the 2 songs in without removing any of the existing tracks, by changing the map defaults for an area to track 90 (5A - Town 2) and track 91 (5B - Town 3). Both of these addresses are blank, so would normally just result in silence, but thanks to the MSU-1 mod, we can play the PCM tracks for those addresses!

Sounds simple right? And there are already a few tools out there capable of doing this type of edit!

So after MUCH MUCH work, and playing around with Zone Doctor CE (https://www.romhacking.net/utilities/1237/), I finally manage to finish the mod (Zone Doctor just did not want to cooperate with actually saving the edits I made, turns out it will only save the FIRST time you click save, it won't actually save any changes after that no matter how often you click save).

I set Town 2 to play in the classroom in Narshe (where the NPCs tell you about the game's mechanics and it normally plays the prelude), and Town 3 to play in South Figaro.

Now here's where my problem comes in. The Pub in South Figaro, map id 78 (4E), is set to track 00 by default, rather than the track 42 (Kids Run Through the City Corner) it's set to on every other map in South Figaro.

The map has some kind of code that handles song selection. With the change in default song from track 00 to track 91, Town 3 does play in the pub, and the song even changes to Under Martial Law properly at that point in the game, but the bit where Shadow is introduced on your first visit to the town? It doesn't play Shadow's Theme anymore. Also the song restarts every time you enter the Pub, where it should continue where it was playing before you entered the Pub.

I've been wrestling with this problem for a few days now, and I finally gave up on figuring it out myself. I have no game hacking experience, don't know ASM, and don't even know how to properly use the debugger options in the various emulators.

I'm hoping someone who knows what they're doing could take a look at this and track down where I need to make the change to get the song for the Pub changed properly.

I strongly suspect that what I need is somewhere in the code that handles Shadow's introduction events, though I'm not sure if it would be the bit on the town map, or the bit for his appearance in the Pub itself, but I have no clue how to track it down.

Any help with this would be appreciated.

edale

ok, trying to track the instructions, not that I really understand what I'm finding...:

Set a breakpoint for a write of '2A' to $1301 (RAM location that controls the currently playing track), and used a ROM I had modded the song selection to something else for every map in South Figaro except the Pub, which I kept to the default setting of "00". This way the breakpoint would ONLY trigger when whatever code calls track 2A to play in the Pub triggers.

Here's a chunk of the trace log generated when entering the pub:

c09a99 lda $e1       [0000e1] A:0081 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:205 F:27
c09a9b bpl $9abe     [c09abe] A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:210 F:27
c09abe lda $e1       [0000e1] A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:215 F:27
c09ac0 and #$40               A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:220 F:27
c09ac2 beq $9ad0     [c09ad0] A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:223 F:27
c09ad0 lda $e1       [0000e1] A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:227 F:27
c09ad2 and #$20               A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:232 F:27
c09ad4 beq $9b1b     [c09b1b] A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:235 F:27
c09b1b ldy #$0005             A:0000 X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:240 F:27
c09b1e lda [$e5],y   [caec52] A:0000 X:0000 Y:0005 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:244 F:27
c09b20 sta $ef       [0000ef] A:00b3 X:0000 Y:0005 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:255 F:27
c09b22 dey                    A:00b3 X:0000 Y:0005 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:260 F:27
c09b23 lda [$e5],y   [caec51] A:00b3 X:0000 Y:0004 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:263 F:27
c09b25 sta $ee       [0000ee] A:0081 X:0000 Y:0004 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:273 F:27
c09b27 dey                    A:0081 X:0000 Y:0004 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:278 F:27
c09b28 lda [$e5],y   [caec50] A:0081 X:0000 Y:0003 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:281 F:27
c09b2a sta $ed       [0000ed] A:00b6 X:0000 Y:0003 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:292 F:27
c09b2c dey                    A:00b6 X:0000 Y:0003 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:297 F:27
c09b2d lda [$e5],y   [caec4f] A:00b6 X:0000 Y:0002 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:300 F:27
c09b2f sta $ec       [0000ec] A:00c0 X:0000 Y:0002 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:310 F:27
c09b31 dey                    A:00c0 X:0000 Y:0002 S:15fb D:0000 DB:00 NvMxdIzc V:186 H:315 F:27
c09b32 lda [$e5],y   [caec4e] A:00c0 X:0000 Y:0001 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:318 F:27
c09b34 sta $eb       [0000eb] A:002a X:0000 Y:0001 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:328 F:27
c09b36 dey                    A:002a X:0000 Y:0001 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:333 F:27
c09b37 lda [$e5],y   [caec4d] A:002a X:0000 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:186 H:336 F:27
c09b39 sta $ea       [0000ea] A:00f0 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzc V:187 H:  6 F:27
c09b3b cmp #$31               A:00f0 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzc V:187 H: 11 F:27
c09b3d bcs $9b42     [c09b42] A:00f0 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzC V:187 H: 14 F:27
c09b42 cmp #$35               A:00f0 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzC V:187 H: 19 F:27
c09b44 bcs $9b49     [c09b49] A:00f0 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzC V:187 H: 22 F:27
c09b49 sec                    A:00f0 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzC V:187 H: 26 F:27
c09b4a sbc #$35               A:00f0 X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzC V:187 H: 29 F:27
c09b4c rep #$20               A:00bb X:0000 Y:0000 S:15fb D:0000 DB:00 NvMxdIzC V:187 H: 32 F:27
c09b4e asl a                  A:00bb X:0000 Y:0000 S:15fb D:0000 DB:00 NvmxdIzC V:187 H: 37 F:27
c09b4f tax                    A:0176 X:0000 Y:0000 S:15fb D:0000 DB:00 nvmxdIzc V:187 H: 40 F:27
c09b50 lda $c098c4,x [c09a3a] A:0176 X:0176 Y:0000 S:15fb D:0000 DB:00 nvmxdIzc V:187 H: 43 F:27
c09b54 sta $2a       [00002a] A:b780 X:0176 Y:0000 S:15fb D:0000 DB:00 NvmxdIzc V:187 H: 52 F:27
c09b56 tdc                    A:b780 X:0176 Y:0000 S:15fb D:0000 DB:00 NvmxdIzc V:187 H: 59 F:27
c09b57 sep #$20               A:0000 X:0176 Y:0000 S:15fb D:0000 DB:00 nvmxdIZc V:187 H: 62 F:27
c09b59 jmp ($002a)   [c0002a] A:0000 X:0176 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:187 H: 66 F:27
c0b780 lda $eb       [0000eb] A:0000 X:0176 Y:0000 S:15fb D:0000 DB:00 nvMxdIZc V:187 H: 75 F:27
c0b782 and #$7f               A:002a X:0176 Y:0000 S:15fb D:0000 DB:00 nvMxdIzc V:187 H: 80 F:27
c0b784 sta $1301     [001301] A:002a X:0176 Y:0000 S:15fb D:0000 DB:00 nvMxdIzc V:187 H: 83 F:27
c0b787 sta $1f80     [001f80] A:002a X:0176 Y:0000 S:15fb D:0000 DB:00 nvMxdIzc V:187 H: 89 F:27
c0b78a lda #$ff               A:002a X:0176 Y:0000 S:15fb D:0000 DB:00 nvMxdIzc V:187 H: 96 F:27


I tracked down the commands immediately preceding the write of 2A to $1301:

https://datacrystal.romhacking.net/wiki/Final_Fantasy_VI:ROM_map/Assembly_C0B#C0.2FB780_event_.23.24F0_:_play_song

C0/B780: A5EB    LDA $EB
C0/B782: 297F    AND #$7F
C0/B784: 8D0113  STA $1301


And what's running right before that.
https://datacrystal.romhacking.net/wiki/Final_Fantasy_VI:ROM_map/Assembly_C09#C0.2F9B01_unknow

C0/9B49: 38      SEC (from C0/9B44)
C0/9B4A: E935    SBC #$35       (event commands start at #$35)
C0/9B4C: C220    REP #$20      (16 bit accum./memory)
C0/9B4E: 0A      ASL A
C0/9B4F: AA      TAX
C0/9B50: BFC498C0 LDA $C098C4,X  (Loads pointer for general action X)
C0/9B54: 852A    STA $2A (Stores it in $2A)
C0/9B56: 7B      TDC
C0/9B57: E220    SEP #$20      (8 bit accum./memory)
C0/9B59: 6C2A00  JMP ($002A)    (after the TAX, TDC SEP #$20 JMP ($98C4,X) would've worked)

But I can't really make sense of this.

If I can find the hex location in the ROM to edit to change the write of '2A' to be a write of '5B' that would be ideal. If not, if I can find the bank location Insidious will code the ASM to make the change for me, but I need to find the point to change before he can do that.

Again, any help would be appreciated.

mziab

The relevant code which loads the track number from ROM is this part of your first trace, just a bit further up:

c09b32 lda [$e5],y   [caec4e] A:00c0 X:0000 Y:0001 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:318 F:27
c09b34 sta $eb       [0000eb] A:002a X:0000 Y:0001 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:328 F:27


In short, the data you want to change is at caec4e, i.e. aec4e in a headerless ROM.

edale

Quote from: mziab on December 21, 2017, 03:53:56 AM
The relevant code which loads the track number from ROM is this part of your first trace, just a bit further up:

c09b32 lda [$e5],y   [caec4e] A:00c0 X:0000 Y:0001 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:318 F:27
c09b34 sta $eb       [0000eb] A:002a X:0000 Y:0001 S:15fb D:0000 DB:00 nvMxdIzc V:186 H:328 F:27


In short, the data you want to change is at caec4e, i.e. aec4e in a headerless ROM.
Alright!

Hex edited aec4e from '2A' to '5B' (to match what I had the rest of the town set to) in my modded ROM, loaded the ROM up... and it worked perfectly! Still need to test the hell out of it to make sure Shadow's Theme and Under Martial Law still play properly at the appropriate parts (and to make sure that covers all the times '2A' is called in the Pub, testing in WoB atm, hopefully WoR's Pub uses the same subroutine for music), but I'm not foreseeing any problems.

Is there any chance you could tell me how you figured that out? I'm guessing it has to do with the "A:002a" portion, but I don't actually know what that means...

I'm asking because now that I know a way to properly change some of these '00' default track maps, it opens the gate to changing the song for some of the other towns that I had given up on (particularly WoB Mobliz, of which pretty much every building has '00' for the default track, and give similar problems to the Pub).

God, when I first thought up this mod I figured, "Oh, this will be a 5 minute edit with Zone Doctor, easy." That was just over a month ago, lol. I have learned a fair amount in the interim though. :)

And fun fact, the 2 second trace I ran when entering the pub to trigger the break point... generated a 1.5 MILLION line trace log. Wow.

mziab

Quote from: edale on December 21, 2017, 07:17:57 AM
Is there any chance you could tell me how you figured that out? I'm guessing it has to do with the "A:002a" portion, but I don't actually know what that means...

Well, once you know the basics of 65816 assembly, it's dead simple really. A is a CPU register called the accumulator. You load data to it using the LDA opcode and store its contents to RAM using the STA opcode. Notice how after the first line I pasted the value of A changed. That's because it was loaded using that LDA opcode.

Another thing is that you were looking at the very end of the process, but the game doesn't load and store the value in one go. It first loads the value from ROM, stores it to $eb (the code I pasted) and the value is actually used a bit later (the snippet you posted). Basically, in this case I searched backwards for any stores to $eb. Hope this helps. If not, I'm sure someone else can give you a more detailed explanation.

edale

Well I don't even know the basics of 65816 assembly, and I was able to do this for a different area completely on my own now! In under 5 minutes even!

Now that I know WHAT to look for, tracing it back to the hex code (and it's incredibly useful for me that it actually lists the direct hex address, even if it adds a 'c' to the start) is easy.

Figuring out how to get that trace was well worth it, I have a strong enough base in IT to get most of the way through figuring out this kind of stuff, but really needed help figuring out that last step, lol. I'll be sure to credit you when I get this finished and released (although it's unclear atm if this will be released separately, or as an option in the Dancing Mad patch installer).

Also, Holy crap. I'm made that change to the relic shop in WoB Mobliz, expecting to need to do it several more times for the other buildings... The one change fixed it for the entire town! Even though each of the buildings are completely different maps! Awesome!

mziab

Great to hear you were able to use that piece of information.

Basic assembly isn't really all that hard, it's mostly a matter of understanding how the CPU works and what each opcode does. When all is said and done, a CPU is just a glorified calculator and programs are just a sequence of simple arithmetic or logical operations. In some respects this might be easier to pick up than higher-level programming languages, but on the other hand, there is no hand-holding and you need to know what you're doing and what to watch out for. It starts getting more complicated with stuff like VWF routines or figuring out compression algorithms, but the "where does this data come from" variety is usually simple enough, unless the game does something really wonky.

Incidentally, caec4e is a HiROM address. Basically, in HiROM what you see in your ROM starts at C0:0000 in the SNES memory map. So if you substract that from the SNES address, you get the actual ROM address (provided there's no header). And conversely, add this to a raw ROM address and you get the SNES address. Some games use LoROM, which is a bit less intuitive. Regardless, you can use something like Lunar Address to convert addresses back and forth. This explanation is a bit simplified to make it more digestible, but that's more or less the gist of it.

Lenophis

So the long and short of it, the code you linked is the event script handler. When any map loads, it has an event attached to it. Most of the time it will call CA/5EB3 which just returns (ie, it does nothing). South Figaro's Pub has a specific event:
CA/EC39: C0    If ($1E80($1CC) [$1EB9, bit 4] is set), branch to $CAEC4F
CA/EC3F: C0    If ($1E80($305) [$1EE0, bit 5] is clear), branch to $CAEC4D
CA/EC45: F0    Play song 7 (Shadow), (high bit clear), full volume
CA/EC47: C0    If ($1E80($127) [$1EA4, bit 7] is clear), branch to $CAEC4F
CA/EC4D: F0    Play song 42 (Kids Run Through the City Corner), (high bit clear), full volume
CA/EC4F: C0    If ($1E80($1B6) [$1EB6, bit 6] is set), branch to $CA5EB3 (simply returns)
CA/EC55: C0    If ($1E80($303) [$1EE0, bit 3] is clear), branch to $CA5EB3 (simply returns)
CA/EC5B: C0    If ($1E80($0A4) [$1E94, bit 4] is set), branch to $CA5EB3 (simply returns)
CA/EC61: 18    Begin action queue for character $18 (NPC $18), 61 bytes long
CA/EC63: E0        Pause for 4 * 255 (1020) frames
CA/EC65: D5        Set vehicle/entity's position to (26, 53)
CA/EC68: 88        Move vehicle/entity up 3 tiles
CA/EC69: 81        Move vehicle/entity right 1 tile
CA/EC6A: 88        Move vehicle/entity up 3 tiles
CA/EC6B: 83        Move vehicle/entity left 1 tile
CA/EC6C: A3        Move vehicle/entity left/up 1x1 tiles
CA/EC6D: A3        Move vehicle/entity left/up 1x1 tiles
CA/EC6E: A3        Move vehicle/entity left/up 1x1 tiles
CA/EC6F: 83        Move vehicle/entity left 1 tile
CA/EC70: 88        Move vehicle/entity up 3 tiles
CA/EC71: 8D        Move vehicle/entity right 4 tiles
CA/EC72: C8        Set object layering priority to 2 (low nibble 2)
CA/EC74: CC        Turn vehicle/entity up
CA/EC75: E0        Pause for 4 * 8 (32) frames
CA/EC77: 80        Move vehicle/entity up 1 tile
CA/EC78: CF        Turn vehicle/entity left
CA/EC79: E0        Pause for 4 * 48 (192) frames
CA/EC7B: CE        Turn vehicle/entity down
CA/EC7C: E0        Pause for 4 * 8 (32) frames
CA/EC7E: FA        Pseudo-randomly choose to branch 6 bytes backwards ($CAEC78) with 50 % chance
CA/EC80: CC        Turn vehicle/entity up
CA/EC81: E0        Pause for 4 * 10 (40) frames
CA/EC83: FA        Pseudo-randomly choose to branch 11 bytes backwards ($CAEC78) with 50 % chance
CA/EC85: E0        Pause for 4 * 192 (768) frames
CA/EC87: C3        Set vehicle/entity's event speed to fast
CA/EC88: 82        Move vehicle/entity down 1 tile
CA/EC89: C8        Set object layering priority to 0 (low nibble 0)
CA/EC8B: 8F        Move vehicle/entity left 4 tiles
CA/EC8C: 8A        Move vehicle/entity down 3 tiles
CA/EC8D: 81        Move vehicle/entity right 1 tile
CA/EC8E: A1        Move vehicle/entity right/down 1x1 tiles
CA/EC8F: A1        Move vehicle/entity right/down 1x1 tiles
CA/EC90: A1        Move vehicle/entity right/down 1x1 tiles
CA/EC91: 81        Move vehicle/entity right 1 tile
CA/EC92: 8A        Move vehicle/entity down 3 tiles
CA/EC93: 83        Move vehicle/entity left 1 tile
CA/EC94: 8A        Move vehicle/entity down 3 tiles
CA/EC95: D5        Set vehicle/entity's position to (81, 17)
CA/EC98: E0        Pause for 4 * 255 (1020) frames
CA/EC9A: FA        Pseudo-randomly choose to branch 2 bytes backwards ($CAEC98) with 50 % chance
CA/EC9C: C2        Set vehicle/entity's event speed to normal
CA/EC9D: FC        Branch 58 bytes backwards ($CAEC63)
CA/EC9F: FF        End queue
CA/ECA0: FE    Return


The very first check is whether or not the "song override" bit is set. If it is, whatever song is playing will continue to play. The next check is a presence bit specific to Shadow for this map. If it's set, Shadow's theme will play. Otherwise it will be Kids Run Through the City Corner. From there it's just other logic not relevant to what you are asking about.


https://ff6randomizer.codeplex.com/ - Randomize your FF6 experience!

assassin

and regarding the CA/EC47 check:
http://web.archive.org/web/20130925043439/http://novaliaspirit.99k.org:80/ff6/file/ff6_snes_event_bits.txt
(EDIT: or a slightly newer version: https://www.ff6hacking.com/wiki/doku.php?id=ff3:ff3us:doc:game)

it's the event code's way of doing an unconditional branch.

edale

So I figured I'd post the results of the help I got in here.

https://mega.nz/#F!PJZg1IRb!kS3ECIgKhAkc-qHWkVGRAg

This patch needs to be applied to a ROM that's already had the Dancing Mad MSU-1 mod applied.
https://github.com/Insidious611/DancingMadFF6/releases

Reintroduces 4 songs to the game that were written for the game, but cut from the final product.

Town 2 - WoB Mobliz
Town 3 - non-occupied South Figaro
Troian Horse - WoB Nikeah (this song fits the town so well I'd almost think that was where it was originally written for)
Approaching Sentiment - Classroom in Narshe (not at beginning of game, but subsequent visits to the town)

My patch will probably end up offered as an option in the Dancing Mad installer, so not bothering to create a full thread for it (though I will if Insidious decides not to include it in his installer).

Enjoy and Merry Christmas!

*edit- Link updated, patch renamed from "Alternate Tracks" to "Cut Songs Restoration", to better reflect what the mod does, no changes to the mod itself.

*edit2- This mod is now included as an optional component in the FFVI Dancing Mad MSU-1 mod's installer.