Here are some small projects to give you some tips and tricks on how to think like an ASM hacker.
One of FE5’s most notable features is its pitch-black fog. Accompanying this fog is a change to the backgrounds shown during battles. It’s easy to change which chapters have fog, simply playing with a byte in the chapter’s chapter data. Where do we go to edit which chapters have matching night backgrounds, though?
Let’s start by making a list of chapters that use these backgrounds:
Chapter 2x
Chapter 4x
Chapter 8x
Chapter 11x
Chapter 12
Chapter 12x
Chapter 14x
Chapter 24x
We should probably convert these into their chapter IDs. I’ve got a set of NMMs for FE5 floating around that includes chapter data stuff.
$02 - Chapter 2x
$05 - Chapter 4x
$0A - Chapter 8x
$0E - Chapter 11x
$0F - Chapter 12
$10 - Chapter 12x
$13 - Chapter 14x
$21 - Chapter 24x
If we’re lucky, there’ll be a table in the game that contains this string of bytes. Let’s try looking in bsnes-plus’s memory veiwer or a hex editor for 02 05 0A 0E 0F 10 13 21
.
No dice. Let’s try again but with each chapter ID taking up a word. Sometimes it’s more convenient to load a whole word even if the data is only a byte. When loading a byte but your register size is 16-bit, you’d need to AND the register with $FF to strip the byte. Alternatively, you can set the register to 8-bit, load the data, and then switch back to 16-bit, but this is slow.
So, we search for 02 00 05 00 0A 00 0E 00 0F 00 10 00 13 00 21 00
.
No luck, either. We’re going to need a different way to find how the game checks for these. We were searching before for a list of chapter IDs because it’d be likely that the game checks for specific chapters when determining if the chapter uses a night background. Why don’t we check for reads to the current chapter’s ID when initiating a battle?
Let’s boot up FE5. Conveniently FE5’s first chapter starts with a character in range so it’s super easy to set this up. Move Eyvel over to attack the enemy soldier, go through with attacking until you get to the battle forecast. Open up the debugger, start the battle, and click the Break
button as the screen is transitioning to black.
We’re going to have to cheat on the learning experience a bit here. Rather than have you find where in RAM the current chapter ID is stored, I’m going to just tell you: $000E11
. Part of being able to understand what is written/read in RAM is being able to understand the routines that read/write there. We’re trying to learn ASM here, which’ll help us learn how to interpret these things that read/write to RAM, which’ll help us write ASM, etc. etc. It’s a big cycle.
Next, in the main emulator window, set a savestate (Tools>Save Quick State
and pick a slot). We’ll use this in case we don’t find what we’re looking for the first time, if we accidentally skip past it, or if we want a chance to trace through things again.
Back in the debugger window, hit Run
.
Let the game run through the battle without doing anything. We want bsnes-plus to run through all the involved code first so that it can do its highlighting stuff. Afterward, hit Break
again and load your savestate (in the main emulator window, Tools>Load Quick State
and select the slot you used before.)
Now we’re going to do it for real. Open up the breakpoint editor and type E11
into the first address box. You can omit leading zeros in addresses. Click the R
checkbox to break on reads to E11
.
Hit Run
in the debugger and let’s see what happens.
The first break you get should looks something like:
...
83c4cf lda #$0031
83c4d2 sta $2f
83c4d4 lda $0e11
83c4d7 jsl $848933
83c4db lda $2f
83c4dd beq $c54a
83c4df lda $7e4fc4
83c4e3 bne $c541
83c4e5 lda $0345
83c4e8 and #$00ff
83c4eb bne $c54a
83c4ed php
83c4ee phb
83c4ef sep #$20
83c4f1 lda #$487e
It should have stopped at 83c4d4 lda $0e11
. I’ve got no idea of what any of this is. Let’s see what that jsl $848933
does.
Hitting Step
twice in the debugger should stop in the routine. You can step through the routine to see what it’s doing. The effect of this routine is that it writes a pointer at $2F. Step to the end of the routine (the rtl
) and pull up the memory editor. At $2F there should be the bytes 31 88 84
(or something similar). If we reverse these bytes and put them together we get $848831
. Let’s go there in the memory editor by typing 848831
into the address box.
Well, the data here doesn’t look like what we’re looking for. The data doesn’t seem like a set of palettes (The first word here is 00 80
or $8000
and colors on the SNES don’t use the upper bit.), an image (it’s much too small, even for a compressed image, which would end with $FF), or a table for graphics.
I don’t think this is what we’re looking for. Before we spend a bunch of time here, make a note somewhere of the start the routine we broke on ($83c4cf
). Let’s click Run
again.
Chances are you’ve ended up at the same place again. As it turns out, this routine seems like it’s run every frame, so it really isn’t what we wanted. If you set a break on read to E11
and watch what happens when normally playing a chapter you’ll get hits all the time. I saw this coming and that’s why I had you set your savestate during the fade out before battles. This way, you’ll hit this point as few times as possible.
Anyway, meta knowledge aside, we just need to keep hitting Run
every time it breaks until we hit a routine we haven’t seen before.
It might take a while, but you should end up at a routine that looks like this
968189 pha
96818a lda #$0000
96818d lda $0e11
968190 cmp #$0002
968193 beq $81bb
968195 cmp #$0005
968198 beq $81bb
96819a cmp #$000a
96819d beq $81bb
96819f cmp #$000e
9681a2 beq $81bb
9681a4 cmp #$000f
9681a7 beq $81bb
9681a9 cmp #$0010
9681ac beq $81bb
with the cursor on the 96818d lda $0e11
. This routine is a big list of comparisons before returning with carry set or clear. Here’s the full routine:
968189 pha
96818a lda #$0000
96818d lda $0e11
968190 cmp #$0002
968193 beq $81bb
968195 cmp #$0005
968198 beq $81bb
96819a cmp #$000a
96819d beq $81bb
96819f cmp #$000e
9681a2 beq $81bb
9681a4 cmp #$000f
9681a7 beq $81bb
9681a9 cmp #$0010
9681ac beq $81bb
9681ae cmp #$0013
9681b1 beq $81bb
9681b3 cmp #$0021
9681b6 beq $81bb
9681b8 clc
9681b9 bra $81bc
9681bb sec
9681bc pla
9681bd rtl
Your disassembler window probably doesn’t have the 9681bb sec
because it’s never been encountered yet.
Let’s see if this is what we’re looking for. Click Step
until you reach the first beq
. While the disassembler has this instruction highlighted green, click the checkbox for the Z
flag in the debugger. Click step and you should arrive at 9681bb
. Hit Run
and watch what happens.
There we go! The night background appears behind our battlers. If you were paying attention to the routine, you might’ve noticed that it has a comparison for each chapter that’s supposed to be a night chapter. This big block of comparisons is ugly and large, but ultimately is a very fast solution if there are only a handful of things to check. I’m going to break down this routine into some easily-digestable pieces.
prlNightChapterCheck ; 96/8189
; Assembler info
.al
.autsiz
; Routine
; Save A because we're using it
pha
; This isn't needed and is probably
; a compiler derp
lda #$0000
; Get current chapter and compare
; to our night chapters
lda $0E11
cmp #$0002 ; 2x
beq _Found
cmp #$0005 ; 4x
beq _Found
cmp #$000A ; 8x
beq _Found
cmp #$000E ; 11x
beq _Found
cmp #$000F ; 12
beq _Found
cmp #$0010 ; 12x
beq _Found
cmp #$0013 ; 14x
beq _Found
cmp #$0021 ; 24x
beq _Found
; If the CPU reaches here, then the
; current chapter isn't a night chapter,
; so we return with carry clear
clc
bra _End
; If the CPU reaches here, then the
; current chapter is a night chapter,
; so we return with carry set
_Found
sec
_End
pla
rtl
Hardcoding a bunch of chapter IDs to be night chapters is very inconvenient. Let’s change this to read from a table of chapter IDs so that it’s easy to edit which chapter is a night chapter. We know that the routine that calls this night chapter check expects that it return with carry clear if it’s not a night chapter and carry set if it is a night chapter.
Let’s write some pseudocode:
x = 0
while true:
get table_entry from table+x
if table_entry == table_end:
return false
elif table_entry == current_chapter:
return true
x += 1
There are some important things for us to think about before we get to writing our routine. First, is this routine called via JSL or JSR? If it was called with JSR then we’d have to do one of three things:
- We could try fitting our replacement routine in the space used by the original routine.
- We could use the space used by the original routine to jump out into free space.
- We could replace all calls to this routine with calls to our new routine, which we’d be putting in free space in the same bank.
The first option is hard to pull off with small original routines. The third option is impossible if there isn’t enough free space within the same bank. Luckily for us, the routine we’re replacing is called via JSL, giving us slightly better options:
- We could try fitting our replacement routine in the space used by the original routine.
- We could use the space used by the original routine to jump out into free space.
- We could replace all calls to this routine with calls to our new routine, which we could put in free space anywhere in the ROM.
Replacing all calls to a routine can be incredibly difficult. Not every call is easy to find. Pointers to routines might be stored in pointer tables rather than baked-in to ASM, the pointers might be calculated by adding an offset to some base, etc.
Before we get too distracted, let’s move on to the second thing to consider: What will this table look like?
What will each entry look like? How will it end?
We’ve got an idea of what each entry should be like: Each should probably be the chapter ID of a night chapter. We know that chapter IDs aren’t ever going to be more than a byte, so we can save a considerable amount of space by making each entry in our table only a byte.
The ending of a table is important because we need some way to tell if we’ve hit the end of the table. We could also check if we’ve reached the end of the table by comparing our offset in the table to the length of the table. We’re going to go with having an entry that signifies the end because it’s simpler and faster.
When we load an entry, the lda
opcode sets two flags depending on the data: n
, the negative flag, and z
, the zero flag. These give us two special types of values we can use: We can have the end entry be $00 and use beq
to hop to our return false
part. We can also use a byte with its upper bit set (remember, n
doesn’t mean that a number is a signed negative number, only that the upper bit is set.) like $80 or $FF or similar along with bmi
to get to our return false
part.
There are many different ways to do this, these are just the best options I can think of for how I want to do this. There are an infinite number of solutions to most ASM problems.
Anyway, let’s think about these two options. For the first, $00 makes a bad ending entry because the first chapter of the game has the ID $00. Our new system would exclude chapter 1 from being a night chapter. If this doesn’t bother you, you’re free to use it.
The other option, a negative number, is much better in my opinion. This leaves us with $00-$7F available as chapters. That’s more than we could ever hope to use. I’m going to go with this solution.
Let’s do this. Create a new file in your text editor. We need to give our table a descriptive name so we can recognize its purpose at a glance. I’ve chosen aNightChapterTable
for mine. The a
at the beginning is a reminder that this thing is an array. It’s good to give yourself hints as to what the purpose, usage, and layout of something is.
After coming up with a name we should probably include some comments on what the table is, its format, and how it ends.
Here’s my table file so far:
aNightChapterTable
; This table is for determining if a
; chapter uses a darker (nighttime)
; battle background. If the chapter's
; ID is in the table, battles will use
; the night backgrounds. The table is ended
; with a byte with its uppermost bit set.
Next, let’s build the body of the table. I’m going to keep the vanilla chapters in it for now.
; These are the vanilla chapters that
; use the night battle backgrounds
.byte 2 ; 2x
.byte 5 ; 4x
.byte 10 ; 8x
.byte 14 ; 11x
.byte 15 ; 12
.byte 16 ; 12x
.byte 19 ; 14x
.byte 33 ; 24x
I wrote out the chapter IDs in decimal because I was lazy and happened to be reading them from a text file. You can specify them in hexadecimal, binary, or even not at all if you don’t want the vanilla chapters in the table.
Then, I added an entry for testing. Rather than playing to one of the chapters above, I’d rather be able to immediately see the results of my work, so I added:
; Let's have an entry for chapter 1
.byte 0
I ended to table with the ending byte:
.char -1 ; This is the terminator
Here’s the whole thing:
aNightChapterTable
; This table is for determining if a
; chapter uses a darker (nighttime)
; battle background. If the chapter's
; ID is in the table, battles will use
; the night backgrounds. The table is ended
; with a byte with its uppermost bit set.
; These are the vanilla chapters that
; use the night battle backgrounds
.byte 2 ; 2x
.byte 5 ; 4x
.byte 10 ; 8x
.byte 14 ; 11x
.byte 15 ; 12
.byte 16 ; 12x
.byte 19 ; 14x
.byte 33 ; 24x
; Let's have an entry for chapter 1
.byte 0
.char -1 ; This is the terminator
Alright, cool. We’ve got a table, now let’s build the thing that reads from it.
Once again we start with a name. I’m going to name this the same name I gave to the vanilla routine, as it serves the same purpose. Next, we need to figure out the register sizes this routine starts with. From breaking earlier we have a printout in our debugger window that shows the flags at the time of the break:
96818d lda $0e11 [800e11] A:0000 X:0012 Y:0000 S:1fdc D:0000 DB:80 nvmxdiZc V: 21 H:125 F: 4
Notice the part that says nvmxdiZc
? This is a representation of the flags, with a lowercase letter meaning that it’s unset. A capital letter means that the flag is set. We see that this routine entered with all registers as 16-bit registers, so our routine might start something like:
prlNightChapterCheck
; Assembler info
.al
.xl
.autsiz
; Routine
After this comes the pushing of registers we need to preserve.
Although we know that this routine is entered with 16-bit registers that might not always be the case, so we have to set things up to handle that. We start by pushing processor flags because we need to leave with sizes being the same as how we entered.
You can always push 8-bit registers like DB
or K
without knowing the size of A
or X
/Y
. Typically we push these first, and then set register sizes before pushing A
and/or X
/Y
. Here we do just that, setting all registers to 8-bit and then pushing them. We set our sizes to 8-bit here because our entries are 8-bit and we won’t have more entries than there are chapters, so X
/Y
could be 8-bit too.
So far, we have:
prlNightChapterCheck
; Assembler info
.al
.xl
.autsiz
; Routine
; We generally push p first
; because subsequent opcodes will
; affect flags
php
; We're going to change DB to the
; bank our table is in.
phb
; Chapter numbers are bytes
; There really isn't a chance for
; there to be more than 255 chapters
sep #$30
pha
phx
Next up, we should set DB
to the bank out table is going to be in. We don’t know where that’ll be, specifically, but the assembler will figure it out.
Remember that the best way to set DB
is by using plb
. We can load the bank into A
, push it, and then pull it into DB
:
; Set DB to our table's bank
lda #(aNightChapterTable>>16)
pha
plb
64tass is supposed to have a special thing for extracting the bank from an address, but I can never get it to work as expected, so we shift our (24-bit) pointer by 16 to end up with an 8-bit bank byte.
This is a good time to mention that 64tass is really good about doing math on things like this.
Up next, we prepare a loop counter to use:
; We use X as a loop counter and we start at 0
ldx #$00
Now we build the loop. Recall the pseudocode from earlier:
x = 0
while true:
get table_entry from table+x
if table_entry == table_end:
return false
elif table_entry == current_chapter:
return true
x += 1
Since we’re at the start of the loop, we should put a label here so that we can come back here.
Then, we grab an entry from the table. I mentioned that lda
sets flags when it loads data, so we can immediately use a conditional branch afterward to jump if we’ve hit the end of the table.
Next, we need to compare the value we loaded to the current chapter’s ID. If they match, we need to jump to the part that returns true.
After that we need to set up what happens if neither of the above things happened. We increment our loop counter by the size of an entry and then we jump back to the start of the loop.
Lucky for us, the size of our entries is a single byte, and the 65816 has a convenient opcode for adding one: inc
. Here’s a look at how I’m doing this:
; An underscore at the start of a label means that the label
; is local and can reuse label names.
_Loop
; We grab an entry from the table and check if it's the end
lda <>aNightChapterTable,x
bmi _NotFound
; Compare to our target chapter number
cmp $0E11
beq _Found
; If the CPU reaches here, the table entry didn't match
; our target chapter number, so we increment the loop
; counter and try again
inc x
bra _Loop
Here the <>
before aNightChapterTable,x
means get the lower 16 bits of this
and keeps the assembler from making this a longer opcode. We can do this because we set DB
earlier.
You can see that the conditional branches refer to two labels, _NotFound
and _Found
. Let’s define them:
_Found
; If the CPU reaches here then the table entry matched
; the target chapter, and we need to wrap up and return
; with the carry flag set.
; Pull our registers in the order they were pushed
plx
pla
plb
plp
; We return with carry set
sec
rtl
_NotFound
; If the CPU reaches here then the target chapter was not found
; in the table. We need to wrap up and return with the carry flag clear.
; Pull our registers in the order they were pushed
plx
pla
plb
plp
; We return with carry clear
clc
rtl
And then, all together:
prlNightChapterCheck
; Assembler info
.al
.xl
.autsiz
; Routine
; We generally push p first
; because subsequent opcodes will
; affect flags
php
; We're going to change DB to the
; bank our table is in.
phb
; Chapter numbers are bytes
; There really isn't a chance for
; there to be more than 255 chapters
sep #$30
pha
phx
; Set DB to our table's bank
lda #(aNightChapterTable>>16)
pha
plb
; We use X as a loop counter and we start at 0
ldx #$00
; An underscore at the start of a label means that the label
; is local and can reuse label names.
_Loop
; We grab an entry from the table and check if it's the end
lda <>aNightChapterTable,x
bmi _NotFound
; Compare to our target chapter number
cmp $0E11
beq _Found
; If the CPU reaches here, the table entry didn't match
; our target chapter number, so we increment the loop
; counter and try again
inc x
bra _Loop
_Found
; If the CPU reaches here then the table entry matched
; the target chapter, and we need to wrap up and return
; with the carry flag set.
; Pull our registers in the order they were pushed
plx
pla
plb
plp
; We return with carry set
sec
rtl
_NotFound
; If the CPU reaches here then the target chapter was not found
; in the table. We need to wrap up and return with the carry flag clear.
; Pull our registers in the order they were pushed
plx
pla
plb
plp
; We return with carry clear
clc
rtl
You could optimize this routine a bit by switching up how things are pushed and then pulled at the end, but we’re not going to get into that.
Now we need some way to install out routine and table. Back in the tips and tricks section I showed an example buildfile. Let’s grab something to use as a base.
.cpu "65816"
* = $000000
.binary "fe5.sfc"
; Place your inclusions that go in fixed locations here
; Place your inclusions that go wherever here
* = $1FB704
.logical $BFB704
.here
$1FB704
is a really large block of free space that’s useful to remember, similar to FE8’s $B2A610
. Let’s put our things into this free space.
; Place your inclusions that go wherever here
* = $1FB704
.logical $BFB704
.include "NightChapterCheck.asm"
.include "NightChapterTable.asm"
.here
Next, we need to have the game call our new routine from the old one.
We could have a separate file that puts our jump in a specific place. We call this a hook.
* = $0B0189
.logical $968189
; Replacing the vanilla night chapter check
jsl prlNightChapterCheck
rtl
.here
This puts our hook over the start of the old routine. Now, when the game jumps to the old night check it’ll then jump to our replacement hack.
Save this as its own file and add it to your buildfile:
.cpu "65816"
* = $000000
.binary "fe5.sfc"
; Place your inclusions that go in fixed locations here
.include "NightChapterCheckHook.asm"
; Place your inclusions that go wherever here
* = $1FB704
.logical $BFB704
.include "NightChapterCheck.asm"
.include "NightChapterTable.asm"
.here
Whether you assemble your stuff with a batch file, a makefile, or via the commandline I don’t really care. What you’d have to assemble this would look something like:
64tass -f -o example1.sfc Example1Buildfile.asm
It should give you a warning about program counter overflows, which you can ignore. It’s because we included the entire base ROM and that crossed literally every bank boundary.
Anyway, assuming you added chapter 1’s ID to the table, you can boot up the assembled ROM and start a fight to see if it worked.
Now, for bonus points we can check to see if our new routine will fit over the original. Going back and looking at the original routine, it takes up $968189-$9681BE
which is $35 bytes.
Opening the ROM in bsnes-plus and using the memory editor to go to $BFB704
, we see that our routine takes up $BFB704-BFB729
or $25 bytes. We’re quite fortunate that our replacement is smaller than the original, because we don’t need to use a hook and can simply overwrite the original routine.
We ditch the hook entirely, move the check routine out of free space and wrap its contents in the
* = $0B0189
.logical $968189
.here
bit.
My buildfile now looks like
.cpu "65816"
* = $000000
.binary "fe5.sfc"
; Place your inclusions that go in fixed locations here
.include "NightChapterCheck.asm"
; Place your inclusions that go wherever here
* = $1FB704
.logical $BFB704
.include "NightChapterTable.asm"
.here
and my NightChapterCheck.asm
file looks like
* = $0B0189
.logical $968189
prlNightChapterCheck
; Assembler info
.al
.xl
.autsiz
; Routine
; We generally push p first
; because subsequent opcodes will
; affect flags
php
; We're going to change DB to the
; bank our table is in.
phb
; Chapter numbers are bytes
; There really isn't a chance for
; there to be more than 255 chapters
sep #$30
pha
phx
; Set DB to our table's bank
lda #(aNightChapterTable>>16)
pha
plb
; We use X as a loop counter and we start at 0
ldx #$00
; An underscore at the start of a label means that the label
; is local and can reuse label names.
_Loop
; We grab an entry from the table and check if it's the end
lda <>aNightChapterTable,x
bmi _NotFound
; Compare to our target chapter number
cmp $0E11
beq _Found
; If the CPU reaches here, the table entry didn't match
; our target chapter number, so we increment the loop
; counter and try again
inc x
bra _Loop
_Found
; If the CPU reaches here then the table entry matched
; the target chapter, and we need to wrap up and return
; with the carry flag set.
; Pull our registers in the order they were pushed
plx
pla
plb
plp
; We return with carry set
sec
rtl
_NotFound
; If the CPU reaches here then the target chapter was not found
; in the table. We need to wrap up and return with the carry flag clear.
; Pull our registers in the order they were pushed
plx
pla
plb
plp
; We return with carry clear
clc
rtl
.here