Hacking a cheat code into a ROM (Avengers in Galactic Storm)

Started by jr121, August 06, 2022, 02:21:13 AM

Previous topic - Next topic

jr121

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.

Bavi_H

I found a memory map for Avengers In Galactic Storm. 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.

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 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.


jr121

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.

Bavi_H

#3
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, 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 and found avengrgs uses the source code in dataeast/deco_mlc.cpp

In deco_mlc.cpp, line 655 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.


jr121

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.

FAST6191

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.

jr121

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.


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.