Switch Characters for Nonaggression
From Sonic Retro
Revision as of 10:50, 13 May 2021 by Ccawley2011
(Original guide by Erik JS)
- Sonic must be the main character
- Player must have all emeralds.
Now, think where the game gets these things. Can you say "RAM"? Yes, and that's why we can change the main character or how many emeralds you have by modifying RAM addresses at VBA's Memory Viewer or Cheat List. So, instead, we now must think like this: in order to access Nonaggression, 030015EA must be 0 and 03000562 must be 7F. Check out RAM section to confirm these addresses. We need addresses because that's what VBASDL seeks while debugging a ROM.
The first thing we need is to have SDL running with SA3 on Altar Emerald with all emeralds a bit before the level ends. You can make a savestate at normal VBA and then use on SDL, but make sure there are no cheats in Cheat List when saving (don't just disable them, remove them!), or SDL will refuse to do some operations later. To "speed up the process", save right before the level ends, in the beginning of the end-of-level results, where the medal appears. Or you can use the battery file. I'm using VBA 1.7.1, but SDL 1.7 (the one from GSCentral) and 1.7.2 (latest official) worked well together. However, launch SDL and make sure you're at Altar Emerald with all emeralds before the level ends. Load that savestate. Now, hit F11. The game becomes paused, and then you can type something in the DOS window that belongs to SDL. Since we know the game checks 030015EA to see if it's 0 or not, we must then set a breakpoint at that address in order to see which instruction in the game code does such check. We need to use bpr, break on read. When active, it'll pause the game and enter the debugger whenever a value is read from that address. In the debugger, enter bpr 030015EA 1. If you did it correctly, the message "Added break on read at 030015ea for 1 bytes" will appear. Now, enter c to continue the emulation.
So, you entered c and it returned some data instead of unpausing the game. Don't worry. That was expected to happen. Do you remember what I said about the character address in RAM section? "It changes instantly", that's what I said. What SDL did was breaking on one of those functions that constantly checks for the char address in order to keep the sprite state aligned with the char value. We know that the relevant function is called at the end of the level, so we need to make SDL ignore those "default" functions. Fortunately, the VBA authors added a db (don't break) command to the SDL (glad they were that smart, huh?). We must set all these "default" functions to the "don't-break" list. Keep entering c until you notice that the first address is being reported again. You'll soon find out that you need to "DB" the following addresses:
To "DB", just enter db 08013356, for instance. Do this for all of them. After that, the game will be "free" to play. Assuming that you're using the savestate made at the end of level results thing (where the medal appears), all you need to do now is to wait until the white fading for the end of the level. After the end of level results, SDL will break right before the game enters that "fake ending" scene. It'll break at 08055492, with the instruction mov r0, #0xF. Take a look at register r0. Is that value familiar to you? Can you say "main character"? But how the hell moving 0xF to r0 can make a check for 0 in 030015EA? Let's continue (enter c). Next break is at 080A55E4 with lsl r1, r0, #0x1C, and none of the registers has the value 030015EA in them. The closer one is r2 with 030015C0. For me, that's enough. Let's check the first break in normal VBA's Disassembler. Close SDL (q command in debugger prompt or press ESC at emu window), launch VBA, launch SA3 and then go to Tools -> Disassemble... At the upper side, you'll see the options Automatic, ARM and THUMB. Select THUMB. In the textbox at right, type the location of the first break from SDL (08055492) and click "GO". That is, our "move to r0" opcode. Scroll up until 0805548C, so you'll be facing this:
You won't have the numbers at left, so don't complain about that - that's my reference =P. I put that way so I can explain for you what each instruction does.
Let's start with line 01. It somehow set r0 to 030015C0, but look at line 02. It adds 2A to r0, so in line 03, r0 would be 030015EA. Does this ring any bells? 080554F4 is probably the location of 030015C0, but how a THUMB interpreter get a 4-byte location with a 2-byte opcode? Simply: 48 is the reference to r0, that's, to attrib a 4-byte value to r0. 19 is how many pointers minus 1 the value is far from the current location. Since a pointer has 4 bytes, then the location is 0805548C + (1A * 4) = 080554F4. Now, onto line 03. It's similar to line 01, but it's a ldrb, and it uses r0 for something. Since in line 04 we're going to lost the main character pointer, then all we can conclude is that this location is what actually caused the SDL to break. But what does this thing mean? By logical reasoning, we can say that line 03 means "r1 = (byte value of r0 location)". So, this is the actual location from where 030015EA was read from. I can't figure what's the purpose of line 04, but moving F into r0 has an effect at the next line, 05. When r0=F, the instruction there means "r0 = r1" (you can confirm this by using the n command in SDL after the break, to execute next instruction, and watching r0/r1 value), so r0 ends with the character value. R4 in line 06 is used afterwards for checking if the player must go to Nonaggression or not. But our purpose is to get rid of the char check, not making Nonaggression free to visit. And line 07 is involved in this, plus that's the location where SDL stopped (that's why it's in a different color from the other lines). It checks r0 (current char value) for 0 (Sonic value). In line 08, there's a conditional jump to 080554BC, which is executed if the result of previous comparison was not equal (branch if not equal). So, if r0 isn't equal to 0, the jump is executed. How do... well, D1 = branch if not equal; 0F = number of opcodes minus 1. Then, 10 is the number of opcodes (groups of 2 bytes) that'll be branched. You start counting at the next opcode, so the formula used there is: 0805549C + (10 * 2) = 080554BC. Let's continue on the next line. Line 09 sets r1 to 03000530. Line 10 looks like r0 = r0 + r1, but it's actually r0 = r1. Why it's an "add" and what the 0x0 represents is a mystery for me, but "r0 = r1" is what happens afterwards. Then, in line 11, r0 becomes 03000562. If you check it out at RAM section, you'll see that this is the address that holds the emeralds. In line 12, r0 becomes the byte value of its location and in line 13 it's checked for the value 7F (all emeralds). In line 14 there's another conditional jump to 080554BC, which probably is the location of the "fake ending" code. I don't know how line 15 works, but that doesn't matter. I hope you understood the explanation I gave about those instructions. We don't need to check the other address from the second break since we have found the char and emerald check on the first. Now, let's modify something.
So, we found out that the opcode at line 08 (0805549A) is what blocks Nonaggression when we have other than Sonic as main character. What we could do, as you may already guessed, is to NOP that jump. NOPing means changing it to a opcode that means "no operation". Unfortunately, I haven't found any NOP THUMB opcode in any GBA game so far. What we can do instead is to revert that jump. Putting D0 in the place of D1 makes the instruction be "branch if equal". Then we would have "if main char is Sonic, jump to fake ending code". That's not what we want. Nonaggression should still be available for Sonic as main character. Why not changing the comparison instruction too? Instead of 00, we would put 05. So it would be like "if main char is Char 05, jump to fake ending". Who's Char 05? Nobody! How does the player access Char 05? Without cheating or using a memory editor, there's no way! So our problems are over! We must then change opcode 2800 at 08055498 to 2805 and D10F at 0805549A to D00F. How do we do this? Using Memory Viewer from VBA. Use it and change values from those memory locations. The opcodes are in Little-Endian, though. After changing them, go back to the disassembler. You should have this result:
If you want to make permanent changes, then open the ROM in an hex editor and change the locations minus 08000000 (so memory location 08001234 would be ROM location 1234, see Introduction and read about pointers if you don't remember that). With the changes made, load that savestate and wait until the end of level. Now, we can go to Nonaggression regardless if we have Sonic as main character or not!