benfrosh

Jungo · @benfrosh

15th Dec 2015 from TwitLonger

Twelve Days Of Romhacking: nATB


So! Let's talk about that 'specific inspiration' that drove me to learn assembly and the terrifying spaghetti code that makes up FFVI. I'm going to go through this like I did when I first came to the problem.

The first thought: I'm fighting the final boss, and I notice something weird. When I put in a command to heal someone who's low health, they keep dying before the heal goes off. I slow down ant pay attention to what's going on, and I realize what's happening: I queue up the command to heal, and 4-5 actions happen before the command fires. All those actions are queued up by people who put in a command and have been waiting for their turn. Why is the queue so long? I'm frustrated, but can't quite put my finger on why.

The second thought: I'm on my third playthrough (I've beaten the hack 11 times as of this writing). I'm fighting Atmaweapon. He has a little speech, during which everyone's ATB gauges are filling up. You remember ATB from yesterday, right? It goes up, when it's full you get to put in your command, which goes in the queue of things to be done, and then the queue is constantly emptied in FIFO manner. Usual stuff. I'm thinking about that while the speech goes on, then I make the key realization. Even when something is going on, everyone's gauges are filling up. That's why the queue was so bad: someone just needed to get their turn before 4 spell animations were done, and they'd pile onto the queue and fill things up. And the faster they got, the longer they'd have to wait before their turn, and the higher chance a monster would fuck something up. Right?

(Right. I didn't have a proof right then, but it's easy to construct one to show ATB is broken in at least some situations in retrospect:

Char A gets a turn every 2 seconds. Monster B gets a turn every 5. All attacks take 5 seconds to animate. If ATB fills during animations:

T = 2: Char A attacks.
T = 5: B gets a turn, queues while animation continues.
T = 7: Animation ends. B animation starts.
T = 9: A gets a turn. Queues attack.
T = 12: Animation ends. A animation starts...

As you can see, despite being 2 1/2 times faster, they get the same amount of turns! This assumes (like FFVI does) that the ATB gauge only starts filling after your animation ends, but it's just as bad if not worse the other way. Left as an exercise to the reader.)


So, I had a precise and specific plan. Stop the ATB gauge from filling when an animation is going on. This had two subquestions:

1) Where is the ATB gauge paused?
2) When does an animation start?

1 is easy: I looked in the ROM map to what variable controls Active/Wait battle mode. In FF, that controls whether the ATB gauge is paused when a menu is open. Since I knew that variable was checked to see if the ATB should pause when a menu's open, I just dug to find what code was using it. (Such a thing would be trivial to find even if you had no knowledge about the ROM map: just toggle the setting and see what memory changes.) The variable was at address $3A8F.

At this time, I was also wondering where the hell I'd put this code when I was done. I decided that since we were pausing when an animation was going, pausing when a menu is open (i.e. defaulting to Wait mode) was acceptable, and I didn't want to use any 'free space' so I didn't conflict with any future mods that might want to incorporate my changes. So I tore out any checks that checked if Wait mode was active and used that to write my code instead. $3A8F became my 'is an animation going' variable.

Looking at the places the old Wait mode variable was used, I quickly found the right piece of code in the map:
C2/1121: AD 41 2F LDA $2F41
C2/1124: 2D 8F 3A AND $3A8F
C2/1127: D0 62 BNE $118B (Exit function if set to wait battle and in a menu)
Easy to find on your own as well if you know how to use a debugger properly, but again: don't do things the hard way if the easy way exists. You're already being a dumbass because you've decided editing a compiled executable is better than finding a different game.

So the change was easy: change AND $3A8F to OR $3A8F. Instead of only pausing if a menu was open AND wait mode was on, we change it to pause if a menu is open OR an animation is going. Easy.

Okay, smartie pants. Question 2. What code calls an attack animation?

This is where I got a bit stumped. If you search the ROM map for 'animation', you'll find C1/EA85 'start animation subroutine.' If you try to put the code there to pause the ATB, it'll pause when any animation plays, even moving a cursor or a menu opening or closing or a character gets a turn or a character is preparing to cast a spell or... it doesn't work. Very uncomfortable. Not ideal. What do we do?

Answer: we dig. Poke around in the ROM map a bit, and you'll find that all the things in C2 are generally game-logicy stuff, and C1 already had animation-type stuff. So the code that says 'do an attack animation' is in C1, because I'm optimistic. In the ROM map, I find something called the 'battle dynamics' table that's referred to by a lot of other code. So I find the code that loads from it, and the ROM map says this:

C1/953B: B276 LDA ($76) (Load the battle dynaimcs command)
C1/953D: C9FF CMP #$FF (Make sure it isn't FF (end script))
C1/953F: F012 BEQ $9553 (If it was, we're done)
C1/9541: 206995 JSR $9569 (Otherwise, do the appropriate command)

So I try rewriting that to increment and decrement the 'is an animation going' counter. This is the annoying part about ROM hacking: you kinda need that old code, but you have to overwrite it to ensure it's called at the right time. So I write my new code in its place, and have it jump to the location I freed up earlier - the menu code that toggles the Active/Wait setting's graphical display. We don't need that, so we just overwrite it and have it return immediately and draw nothing! Problem solved.

So I wrote the code. Here's the original notes I have for the code I wrote, to document it.

C1/953B: 22 95 3B C3 JSL C3/3B95 // the hooks to go to my pause/unpause code
C1/9544: 22 9F 3B C3 JSL C3/3B9F
C2/1124: 0D 8F 3A ORA $3A8F // OR instead of AND in the ATB wait check
C2/247A: 9C 8F 3A STZ $3A8F // initializing flag variable for later use
C2/247C - C2/247F: NOPs to cover unwanted checks
C3/3B8C: Moved up code used for showing Wait mode on, from C3/3B93.
C3/3B95: Code that was previously at C1/953B, with an EE 8F 3A INC $3A8F thrown in at 3B9B
C3/3B9F: CE 8F 3A DEC $3A8F, then code that was previously at C1/9544.

JSL is Jump Subroutine (Long), meaning we're jumping to a part of the code using a 24-bit address instead of 16 bit. 16 bit assumes you stay in C1, C2, wherever you are. This is why functionality tends to be grouped by bank; it makes the jump calls take less space and keeps things simpler. ORA means 'OR my argument (the value at $3A8F) with whatever is in register A. STZ is 'write the value 0 at my argument location'. NOP does nothing - it covers up some code I wasn't using anymore since active/wait was dead. INC is increment (add one), DEC is decrement (subtract one). The idea is from the animation code, it would jump to C3, do the code that I had overwritten previously, then do my increment, then jump back to do the actual command. Once the command was done, it'd jump to C3 again, decrement the flag, then do the cleanup
and finish the turn.

This is like a week of work I just summarized, at this point I've had code that crashed the game, corrupted the graphics, crashed the EMULATOR, did nothing, completely paused the battle and never let it start... I wasn't exactly optimistic that the battle dynamics code would be the right spot, but I had to test it. So, as I had done dozens of time before that, I started up the debugger I was using. I loaded my save, walked up to Atmaweapon.

He started talking. The gauges stopped filling. He finished talking. The gauges started filling. Someone had a turn. They attacked. The gauges paused. The animation finished. The gauges resumed.

It's a dumb reference to something you don't even know, but this is what I wrote at the bottom of the TXT file that had my notes:

The Great Hack was completed on a rainy Wednesday evening.
There were no bugs, no glitches, no sign anything had changed in the code.
But somewhere, deep in the SNES's RAM, a little flag was incrementing and decrementing.
Real, honest-to-goodness logic changes.

Reply · Report Post