Romhacking.net

Romhacking => ROM Hacking Discussion => Topic started by: jr121 on August 06, 2022, 02:21:13 AM

Title: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: jr121 on August 06, 2022, 02:21:13 AM
So I will start out by being upfront and saying I have very little romhacking experience (Which is to say, I've done some exceptionally basic text edits in some old arcade games to sort of familiarize myself with basic hex editing).

Essentially, I'm trying to hack a cheat code into a rom file to create a rudimentary boss hack for Avengers in Galactic Storm (I help run the tournaments for the game, and me and the a few others in the community have expressed interest at getting the game's boss playable in a proper fashion). We have cheat codes that allow him to be played, but due to limits around how the tournaments are run, cheat codes can't be used (Not a rule thing, it's a software issue), so I've been looking into creating a romhack.

The cheat codes let us know that setting hex values 108E48 and 108FF8 to a value of 08 will cause the cursor to start on the unselectable boss character, though a player can freely move off of it, unfortunately, due to it being a MAME rom of several files, it's not a simple edit.

Going through Mame's writexml, files sf_00-0.7k and sf_01-0.7l correspond to the main CPU, while everything is gfx or 'ymz' (Presumably in reference in the MLC's Yamaha sound processor), and unless I'm mistaken (Entirely possible), those are the files I need to tinker with in order to allow this to work. I've used 010 editor to interleave the files, but it's still too small to find that address.

What am I doing wrong, and what can I do to make this change? Any help is appreciated.
Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: Bavi_H on August 06, 2022, 07:10:29 PM
I found a memory map for Avengers In Galactic Storm (http://www.arcaderestoration.com/memorymap/483/). The beginning of this memory map says

address range 000000 to 0FFFFF is ROM
address range 100000 to 11FFFF is RAM

So it sounds like the cheat you are describing is modifying RAM. RAM isn't stored in a file for MAME to load. To make this cheat permanent, you would need to find an appropriate location in the program code ROM where you can insert or modify an instruction to do something like "write the value 8 into the RAM location 108E48".


Questions

1. Can you confirm the specific character select menu you need to modify? (I see there is a Story Mode with Friend Mid-Game Participant, a Story Mode with Foe Mid-Game Participant, and a Versus Mode, and I want to confirm I'm investigating the correct mode.)

2. Can you confirm the name of the boss character you are making playable? (On the character select menus, it looks like setting address 108E48 to have value 8 lets you play as a character called Galenkor, can you confirm that's the boss you wanted?)

3. Can you provide a link to or post the cheat file text?

4. Can you describe the steps to use this cheat? (For example, it sounds like you have to get to the character select menu, then "run" this cheat one time so it will modify the RAM location indicating where the selection cursor is. Is that right?)


Investigation Progress

I found documentation about MAME's debugger (https://docs.mamedev.org/debugger/).

I started MAME with the command line of mame -debug so MAME would start with the debugger activated.

While the game is running you can open the debugger window by pressing the ` key on the keyboard. (If you changed the key settings, open the MAME menu and go to "Input (General)" then "User Interface" and see what the key is for "Break in Debugger".) You can close the debugger window to continue the game.

In the game, I did things like the following.

I opened the debugger and asked it to halt on a write to address 108E48 with this watchpoint command:

wp 108E48,1,w

The debugger opened various times that location was written, but I'm mainly interested in the times when the character select menu is open. Once I was in a character select menu, the address kept getting written over and over so I disabled the watchpoint with debugger commands like this:

wplist
wpdisable 1

While I was still in the character select menu, I set the location to contain value 8 with these debugger commands:

print b@108E48
b@108E48 = 8
print b@108E48

When I closed the debugger window, the selection cursor disappeared, but after pressing one of the arcade buttons, this selected the character Galenkor. In Story mode, it starts a story scene, but then seems to get stuck. In Versus mode, it starts a fight with the player controlling this character.

If I quit the game and start this process over again, when the watchpoint keeps activating over and over in the character select menu, I can see the following instruction had just executed with the following register values:

FC4E MOV.B R12,@(R0,R14)

R0  = 148
R14 = 108D00
R12 = 3

This instruction seems to mean "put the value of R12 into the location (R0 + R14)", in other words, it is storing the value of 3 into the address 108E48.

I want to find some instruction a little before this that I might be able to modify so that the value of 8 will initially end up in the address 108E48. (For example, maybe I can find an instruction that initially loads R12 with 3 and change it to load R12 with 8 instead.)

In the debugger, I dumped some of the instructions around this instruction into a file:

dasm out.asm,f000,1000

The title bar of the debugger window has "Hitachi SH-2" in it, so that sounds like that is the CPU that is used. In a search engine, I searched for "Hitachi SH-2 instruction set" and found the PDF file Hitachi SuperH RISC Engine SH-1/SH-2 Programming Manual (https://antime.kapsi.fi/sega/files/h12p0.pdf) that I'm starting to read.

By the way, I did some of the same watchpoint steps for the other address 108FF8, and it looks like that address is used for the selection cursor for player 2 (or the computer player in a single player game). I'll need to investigate the code for that address as well.

Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: jr121 on August 07, 2022, 02:34:42 AM
Hey, I greatly appreciate the help, the bit about the memory map honestly helps explain a lot, shows the blatant misunderstanding I had with this.

To answer your questions:
1. Versus mode only. According to the cheat file (Which I'll link with question 3) it only works in the versus mode, and I've only tested it with versus mode.
2. Galen-Kor is the game's sole boss and who we're looking to make playable.
3. https://drive.google.com/file/d/1TzKOFm4MK9a1BIA3tT_ueL4-tIs1280j/view?usp=sharing
4. You can activate the cheat as soon as you load up the game. Going into versus mode, the cursor will then default to Galen Kor.
The selection cursor for player 1 is by default at value 03 (Captain America) and player 2's is by default value 04 (Thunderstrike). The way the numbers correspond to the characters is very simple, as value 00 starts at the far left with Minverva, going up to 07 (Supremor) by default, with 08 loading up Galen Kor.
Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: Bavi_H on August 07, 2022, 03:37:50 AM
I continued using execution breakpoints, data watchpoints, and trace logs to work backward through the code that executes before the character select screen to find an appropriate place to change.

I found the following instruction:

93E 7103 ADD #$03,R1

and changed it to this:

93E E108 MOV #$08,R1


What the instruction does

When you press the start button for player 1 or player 2, the code eventually reaches this instruction at address 93E.

If you pressed player 1 start, R1 is 0 before this instruction executes, then the ADD insturction increments R1 by 3. The R1 value eventually causes the selection cursor on the character select screen to start out on the fourth character. (The characters are numbered from left to right as 0 to 7, so 3 refers to the fourth character.)

If you pressed player 2 start, R1 is 1 before this instruction executes, then the ADD instruction adds 3 to R1 to get a value of 4, so player 2's selection cursor starts on the fifth character.

The new MOV instruction sets R1 to 8 no matter what R1 was before. This causes the either player's selection cursor to start out hidden and if you press the button, you get Galen Kor.


Figuring out what file and bytes to change

To find the opcode for the MOV instruction I wanted, I looked in the Hitachi SuperH RISC Engine SH-1/SH-2 Programming Manual (https://antime.kapsi.fi/sega/files/h12p0.pdf), section 5.2 "Instruction Set in Alphabetical Order". On PDF page 56 (printed page 52) I found the "MOV #imm,Rn" instruction is the opcode binary 1110 nnnn iiii iiii (hex En ii), where n is the register number and i is the immediate value. So "MOV #$08, R1" is opcode bytes E1 08.

In the MAME source code, I looked in mame.lst (https://github.com/mamedev/mame/blob/master/src/mame/mame.lst) and found avengrgs uses the source code in dataeast/deco_mlc.cpp

In deco_mlc.cpp, line 655 (https://github.com/mamedev/mame/blob/master/src/mame/dataeast/deco_mlc.cpp#L655) shows a kind of memory map and seems to say the addresses from 00000 to FFFFF come from the files sf_00-0.7k and sf_01-0.7l

I used debugger print statements to check values from the game's CPU addresses and compared to the values in these files. I manged to figure out the pattern to covert a game CPU address into the matching file and offset:

CPU    file  offset
00000     L  0001
00001     L  0000
00002     K  0001
00003     K  0000
00004     L  0003
00005     L  0002
00006     K  0003
00007     K  0002
...
FFFFC     L  7FFF
FFFFD     L  7FFE
FFFFE     K  7FFF
FFFFF     K  7FFE

Using this pattern, CPU addresses 93E and 93F are in file K, offsets 49F and 49E.


How to change

In the file "sf_00-0.7k", goto offset 49E and change the values from 03 71 to 08 E1

Start MAME with the command line mame avengrgs

(If you start MAME without any command line arguments, then select the game from the menu, it will tell you the sf_00-0.7k file has an incorrect checksum and won't start the game.)

On the character select screen, the selection cursor starts out hidden and if you press the button, you get Galen Kor.


In the story mode, if you start a single player game (only player 1 or only player 2) and choose Galen Kor, or if you start a two player game with player 1 as Galen Kor, a story scene will start and Galen Kor will say "What the...??" and point, but the fight never starts. You can press the 3 key on the keyboard (the "Player 3 Start" button) to skip to the next story scene and fight. Be careful to only press the 3 key once the initial story scene reaches the "What the...?? and point" part. (Or at other points in the game, be careful to only press the 3 key after a fight has started.) Pressing the 3 key at other times causes glitches. Update: I thought the previous method was a reliable way to skip through the Story mode, but one time I pressed the 3 key after a fight was in progress and it still glitched the game. So if you try it be aware glitches are possible.


You might want to test the game further to make sure it works well. I don't know if the game checks if its code has been modified and does something like make the game harder or impossible to complete.

Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: jr121 on August 07, 2022, 02:13:34 PM
This is incredibly helpful. Luckily, single player isn't really a concern, since we're purely using this little hack as a way to facilitate tournaments for the game (Though the story mode only intends for you to be able to play as Avengers, who given the way assists are chosen based on whether you're an Avenger or a Kree and selecting Galen Kor brings up the Kree assist menu, it believes he's a Kree and that's more likely why it's crashing. If that value could be changed, he'd probably play through the story mode as normal). Now we're just trying to figure out how to unlock the game's assists to be usable by any characters, and I'll see if I can use any of the info you've given to start making that a reality.

Also, while I know it's not particularly helpful now, the 'Player 3 Start' thing appears to be a debug function the developers left in, as we've found that pressing it normally restarts a match in versus mode, while pressing different combinations of buttons in conjunction with it resets the match and changes the stage you're playing on. We've primarily used it to help make a training mode reset button, but I just bring it up since you refer to it in the edit as a 'Skip Button Glitch,' though it seems to be an intentional decision.
Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: FAST6191 on August 08, 2022, 11:09:37 AM
Good stuff above and how I would approach such a thing. However if it is merely a menu restriction that prevents selection (sometimes play as boss cheats are more character selection forcing affairs) then might be easier to kick that in the head instead.

I don't know (probably could be known from what is above but eh) if the grid is a grid (that is to say X and Y positions with attendant logic) or is a position in long line (simple numbers in this case, more common for inventory) that just so happens to be displayed as a grid on screen (possibly with an if pressed down add however many extra to make it make sense a la the conversion from caps to lower case in ASCII). Either way should be easy enough to find the current position (sounds like you already know one factor in it to make the cheat in the first place).
If you have that then whatever writes the new position will have to have been preceded by some kind of logic to prevent you from selecting it in normal play. In which case chucking a couple of NOPs around for its handling of items adjacent to the normally unselectable character(s) might get you something, might also crash it a bit if you remove too many restrictions but if that is something you can live with then great (tournament play is not like arcade owner has to physically go reset every time those damn kids) and if not then you get to tweak the restrictions instead.

When looking at the handling do also look to see if there is another flag it checks to see if you are allowed -- there might be a hidden debug mode, code remnants of it or something that functions akin to that. Seen that more than once in things and following up on such "unnecessary" forks in the code is one of things if I am teaching finding hidden cheats/menus/whatever. If it is then a flag in memory you might be able to more safely hardcode that with a basic tweak somewhere along the line or extra vblank routine where this current stuff might need something a bit more variable lest you force select a player character with a cheat.
Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: jr121 on August 08, 2022, 09:04:03 PM
Quote from: FAST6191 on August 08, 2022, 11:09:37 AMGood stuff above and how I would approach such a thing. However if it is merely a menu restriction that prevents selection (sometimes play as boss cheats are more character selection forcing affairs) then might be easier to kick that in the head instead.

I don't know (probably could be known from what is above but eh) if the grid is a grid (that is to say X and Y positions with attendant logic) or is a position in long line (simple numbers in this case, more common for inventory) that just so happens to be displayed as a grid on screen (possibly with an if pressed down add however many extra to make it make sense a la the conversion from caps to lower case in ASCII). Either way should be easy enough to find the current position (sounds like you already know one factor in it to make the cheat in the first place).
If you have that then whatever writes the new position will have to have been preceded by some kind of logic to prevent you from selecting it in normal play. In which case chucking a couple of NOPs around for its handling of items adjacent to the normally unselectable character(s) might get you something, might also crash it a bit if you remove too many restrictions but if that is something you can live with then great (tournament play is not like arcade owner has to physically go reset every time those damn kids) and if not then you get to tweak the restrictions instead.

When looking at the handling do also look to see if there is another flag it checks to see if you are allowed -- there might be a hidden debug mode, code remnants of it or something that functions akin to that. Seen that more than once in things and following up on such "unnecessary" forks in the code is one of things if I am teaching finding hidden cheats/menus/whatever. If it is then a flag in memory you might be able to more safely hardcode that with a basic tweak somewhere along the line or extra vblank routine where this current stuff might need something a bit more variable lest you force select a player character with a cheat.

Yeah, it very much is just forcing the game to select the character by default, but only as default, for as I said, you can move off of him if you don't want to play as him, so I do think this is an acceptable change.
The character select is a simple left to right row, with the character on the far left having value 00 and the far right a value of 07.
(https://r.mprd.se/MAME/select/avengrgs.png)

The hack Bavi_H provided changes the default value to be 08 (That of the boss character), which causes the cursor to start off screen and selecting the boss character, while moving off of it immediately corrects to a normal number. Another possible fix would definitely be to increase the value the CS normally views as the max to 08, as the screen is coded to loop from left to right anyway (If you hit right while on the far right, cursor loops to the other side and vise versa), so theoretically it would just make it go offscreen to choose the boss and then loop back around as needed, but that's likely a bit more of an in-depth edit, and as is, we're more concerned with trying to make all assists selectable now.
Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: Bavi_H on August 14, 2022, 06:46:20 AM
I continued investigating to see if there is a way to re-select Galen Kor once you move the cursor away from the default hidden state. I was hoping there are some instructions related to wrapping the value for the cursor. In other words, if there's an instruction that says "if the value is >= 8, change it back to 0" we could change it to check for >= 9, and if there's an instruction that says "if the value is <= -1 set it back to 7" we could change it to set it back to 8.

As mentioned previously, when you're on the character select screen, memory location 108E48 is used for the position of player 1's selection cursor.

I started a one player versus game. At the character select screen, I opened the debugger and entered these commands:

Watchpoint to halt on 108E48 write:
 wp 108E48,1,w
Run until watchpoint is hit:
 go
Start logging instructions to file:
 trace one-loop.asm
Run until watchpoint is hit again:
 go
Stop logging instructions to file:
 trace off
Clear watchpoints:
 wpclear

I opened the one-loop.asm log file in a text editor. I started at the end with the instruction that wrote to 108E48:

FC4E: MOV.B R12,@(R0,R14)
R0 = 148
R14 = 108D00

Using the text editor's find backwards command, I looked for ",R12" to find the instruction that wrote some value into R12. (For this CPU, instructions that end with ",Rn" will write a value to Rn.)

Each time I got to a new instruction, I started another backwards search (or searches) to find where registers were written. I built a little dependency tree:

FC4E: Mem[$108E48] = R12
  F728: R12 = Mem[R0 + R5]
    F710: R0 = $148
    F724: R5 = R13 + R5
      F70C: R13 = $108D00
      F722: R5 = sign extend R5
        F71E: R5 = MACL
          F71C: MACH and MACL = R3 × R5
            F6FC: R3 = $1B0
            F716: R5 = R14
              F6FA: R14 = R4
                F13E: R4 = 0

Note: for the following instructions with constant values, MAME put the address used in square brackets. I used a print command in the debugger to see what the value was.

instruction in trace fileprint commandvalue
0000F710: MOV.W @($0104,PC),R0  [0000F818]   
print w@f818   
   148
0000F70C: MOV.L @($0110,PC),R13 [0000F820]   
print d@f820   
108D00
0000F6FC: MOV.W @($0116,PC),R3  [0000F816]   
print w@f816   
   1B0

When I plugged in the values in the dependency tree from the bottom up, all I learned is the code is effectively just assigning Mem[$108E48] = Mem[$108E48].

I decided to start over and find some code that is checking if the joystick is moved left or right.

In the previously mentioned memory map (http://www.arcaderestoration.com/memorymap/483/), I saw addresses described as INPUTS (hex 400000 to 400003), INPUTS2 (hex 440000 to 440003), and INPUTS3 (hex 440004 to 440007). In the previously mentioned deco_mlc.cpp, line 359 (https://github.com/mamedev/mame/blob/master/src/mame/dataeast/deco_mlc.cpp#L359) and following describe how the bits at these addresses are mapped to joystick directions and buttons.

From the character select screen, I set a watchpoint to halt when address 400000 is read:

wp 400000,1,r

and found this instruction:

3334 MOV.L @R2,R4
R2 = 20400000

(I didn't confirm, but I assume there must be some mirroring so that address 20400000 refers to the same place as address 400000.)

I restarted the game, then started a one player versus game. At the character select screen, I opened the debugger and entered the following commands to log the instructions between a joystick right press and when the cursor address is written:

Watchpoint to halt on input read:
 wp 400000,1,r
Run until watchpoint is hit:
 go
Simulate joystick right press:
 r4 = FFFFFFF7
Start logging instructions to file:
 trace right.asm
Watchpoint to halt on cursor write:
 wp 108E48,1,w
Run until watchpoint is hit:
 go
Stop logging instructions to file:
 trace off
Clear watchpoints:
 wpclear

In a text editor I opened the right.asm log file and, starting from the end, I used the previously described searching backward process. The first search (for ",R12") found this AND instruction and surrounding code:

F840: ADD R12,R0
F842: MOV #$07,R12
F844: BTS $0000F86A
F846: AND R0,R12

I set a breakpoint on F840...

bp f840

...then closed the debugger. Each time I pressed the joystick right or left (pressed the keyboard right arrow key or left arrow key), the debugger popped up at instruction F840 and let me see when this instruction is reached, R0 is +1 if the joystick was pushed right or R0 is -1 (hex FFFFFFFF) if the joystick was pushed left.

Also at this point, R12 is the value used for the selection cursor, and the BTS instruction has a one-instruction delay. Translating the instructions into a higher level pseudocode gets:

F840: ADD R12,R0    ─→ R0 = R12 + R0    ─→ R0 = R12 + R0   ┐ 
F842: MOV #$07,R12  ─→ R12 = 7          ┐                  ├→ R12 = (R12 + R0) AND 7
F844: BTS $0000F86A ┐                   ├→ R12 = R0 AND 7  ┘
F846: AND R0,R12   ─│→ R12 = R0 AND R12 ┘                 
                    └→ If T, goto F86A  ─→ If T, goto F86A ─→ If T, goto F86A

The "AND 7" instruction forces the value to be in the range 0 to 7. I can't think of a way to get the range to be 0 to 8 using the same number of instructions. I think we'd have to find some free space somewhere that we can jump to and overwrite with instructions that use boundary check methods (like "if the value is >= 9, change it back to 0" and "if the value is <= -1, set it back to 8"), then jump back to the original spot.

Title: Re: Hacking a cheat code into a ROM (Avengers in Galactic Storm)
Post by: Bavi_H on August 20, 2022, 09:15:30 AM
Version 1.1 of the mod is now available: Play as Galen Kor in Avengers in Galactic Storm (Versus Mode) (https://www.romhacking.net/hacks/7050/)

In the new version, when you get to the Versus mode character select screen, the selection cursor starts on the default character. Moving the cursor off the left or right edge will hide the cursor to be able to select Galen Kor with button 1, or you can wrap the cursor back around to select normal characters.

As mentioned previously, this required a longer code patch. Here are some notes about that development process.

From a previous trace, I knew that instruction F83C is reached from a branch, so that seemed like a good place to start inserting a modification.

F83C: MOV.B @($04,R15),R0  ; R0 = +1 if joystick right, -1 if joystick left
F83E: TST R5,R5            ; T = (R5 == 0)
F840: ADD R12,R0           ; R0 = cursor + R0
F842: MOV #$07,R12         ; R12 = 7
F844: BTS $F86A            ; Is T set? Yes: After next instruction, goto F86A
F846: AND R0,R12           ; R12 = R0 AND R12
F848: [code continues]     ; No: [code continues]

Because the BTS instruction at F844 has a 1-instruction delay, the AND instruction at F846 will always be executed, but the next instruction at F848 will only be reached if the branch is not taken (when the earlier TST instruction returns false). If the TST instruction returns true, the instruction at F848 isn't reached, that's the point where the branch to F86A is taken instead.

So the instruction slots from F83C to F846 seemed like a good point to try to fit a modification to jump somewhere else to do more instructions. That's a space of 6 instructions to work with.

However, to jump somewhere else that is far away, you have to use a register. And you have to store a constant in two instruction spots that start at an address that is a multiple of 4. And in case the register is used for something else, you would need to push the register value to the stack before changing it, and pop the register value from the stack after you're done. And the JMP instruction has a delay slot, so you probably need to put a NOP after the JMP. As I began to draft some code, I noticed it would take 7 instructions to do all of that, which is too big to fit:

[1]: MOV.L Rn,@-R15    ; push register used for jump
[2]: MOV.L const,Rn    ; ┐ jump somewhere else so we can
[3]: JMP @Rn           ; ┘ execute a longer set of instructions
[4]: NOP               ; delay slot
[5]: const: .dw $xxxx  ; ┐ constant to store the
[6]:        .dw $xxxx  ; ┘ address to jump to
[7]: MOV.L @R15+,Rn    ; pop register used for jump

From previous trace dumps and disassembly dumps I had made, I looked to see if there was a register that was re-written the next time it was used after this F83C to F846 spot. That would mean it would be safe for me to use that register without having to save its value on the stack. I found register R0 was re-written in either branch after this spot, so that let me reduce the code to 5 instructions:

[1]: MOV.L const,R0    ; ┐ jump somewhere else so we can
[2]: JMP @R0           ; ┘ execute a longer set of instructions
[3]: NOP               ; delay slot
[4]: const: .dw $xxxx  ; ┐ constant to store the
[5]:        .dw $xxxx  ; ┘ address to jump to

That is small enough to fit, and get the constant placed at an address that is a multiple of 4:

F83C: NOP               ; don't need this instruction, changed to NOP
F83E: MOV.L const,R0    ; ┐ jump somewhere else so we can
F840: JMP @R0           ; ┘ execute a longer set of instructions
F842: NOP               ; delay slot
F844: const: .dw $xxxx  ; ┐ constant to store the
F846:        .dw $xxxx  ; ┘ address to jump to
F848: [code continues]  ; [code continues]

To look for potential free space, I dumped the entire program ROM

save rom.bin,0,100000

then I opened the dump in a hex editor and looked for patterns that suggested spots might be unused. I found various ranges of 00 bytes, including at the end of program ROM. I decided to put the new patch code at the very end of the program ROM. (Play testers haven't reported any problems, so that further suggests this spot at the end of program ROM was unused.)

The patch code has to do the same thing that the original instructions did, but instead of using the AND method of keeping the cursor in range, it will use boundry checks instead.

Here's the complete before and after code I came up with:



Before


0F83C: 84F4  MOV.B @($04,R15),R0  ; R0 = +1 if joystick right, -1 if joystick left
0F83E: 2558  TST R5,R5            ; T = (R5 == 0)
0F840: 30CC  ADD R12,R0           ; R0 = cursor + R0
0F842: EC07  MOV #$07,R12         ; R12 = 7
0F844: 8D11  BTS $F86A            ; Is T set? Yes: After next instruction, goto F86A
0F846: 2C09  AND R0,R12           ; R12 = R0 AND R12
0F848: []    [code continues]     ; No: [code continues]

[...]                             ; [...]

FFFD4: 0000  .dw $0000            ; some space at the end of
[...]  [ "    "    " ]            ;   CPU ROM that contains
FFFFE: 0000  .dw $0000            ;   zeros, presumed unused.

___________


After


0F83C: 0009  NOP                  ; ┐
0F83E: D001  MOV.L const_FFFD4,R0 ; │
0F840: 402B  JMP R0               ; │ Goto FFFD4
0F842: 0009  NOP                  ; ┘

             const_FFFD4:
0F844: 000F  .dw $000F            ; ┐ (constant)
0F846: FFD4  .dw $FFD4            ; ┘
0F848: []    [code continues]     ;   [code continues]

[...]                             ;   [...]

FFFD4: 84F4  MOV.B @($04,R15),R0  ;   R0 = +1 if joystick right, -1 if joystick left
FFFD6: 30CC  ADD R12,R0           ;   R0 = cursor + R0
FFFD8: 8809  CMP/EQ #$09,R0       ;   Is R0 == 9?
FFFDA: 8B01  BF next1             ;   No: Goto next1
FFFDC: A004  BRA end              ;   Yes: After next instruction, goto end
FFFDE: EC00  MOV #$00,R12         ;   R12 = 0

             next1:
FFFE0: 88FF  CMP/EQ #$FF,R0       ;   Is R0 == -1?
FFFE2: 8B00  BF next2             ;   No: Goto next2
FFFE4: E008  MOV #$08,R0          ;   Yes: R0 = 8

             next2:
FFFE6: 6C03  MOV R0,R12           ;   R12 = R0

             end:
FFFE8: 2558  TST R5,R5            ;   T = (R5 == 0)
FFFEA: 8B02  BF next3             ;   Is T set? No: Goto next3
FFFEC: D002  MOV.L const_F86A,R0  ; ┐
FFFEE: 402B  JMP R0               ; │ Yes: Goto F86A
FFFF0: 0009  NOP                  ; ┘

             next3:
FFFF2: D002  MOV.L const_F848,R0  ; ┐
FFFF4: 402B  JMP R0               ; │ Goto F848
FFFF6: 0009  NOP                  ; ┘

             const_F86A:
FFFF8: 0000  .dw $0000            ; ┐ (constant)
FFFFA: F86A  .dw $F86A            ; ┘

             const_F848:
FFFFC: 0000  .dw $0000            ; ┐ (constant)
FFFFE: F848  .dw $F848            ; ┘