This turned out to be a considerably long post. I apologize in advance.
I remember seeing that tutorial a few years ago.
First, here is a handy link for 6502 Opcodes. I would recommend saving this page as an offline HTML file for easy access. Much like any information that you find online that is helpful and handy, you never know when it might randomly disappear.6502.org: Tutorials and Aids
So, I went through the tutorial. I agree that it might seem confusing for those who are just beginning to try something like this, as it was written to provide the basic guidelines, but also leaves the user to come up with part of the solution themselves in a way, specifically how they determine to put in their jump, and the jump address itself where they decide to put in the custom code.
First, after entering a level, setting the write breakpoint to $00ED in RAM, and grabbing a Mushroom, the debugger freezes at $A3F0, just as it occurred in the tutorial. In the debugger, there is a vertical bar on the left side that shows the RAM and ROM addresses for each line displayed. $A3F0 is 0x10400 in the ROM file.
If you move up a few lines in the debugger, you can see the code the precedes the write to $00ED. There is a 60 (RTS) at $A3C9 (0x103D9), so this block of code that we're dealing with right now begins (or even possibly continues from somewhere else) at $A3CA (0x103DA).
$A3CA:AD 78 05 LDA $0578 = #$02
$A3CD:F0 2B BEQ $A3FA
$A3CF:C9 0F CMP #$0F
$A3D1:30 19 BMI $A3EC
$A3D3:C9 80 CMP #$80
$A3D5:D0 05 BNE $A3DC
$A3D7:EE 77 05 INC $0577 = #$00
$A3DA:D0 16 BNE $A3F2
$A3DC:C9 40 CMP #$40
$A3DE:F0 07 BEQ $A3E7
$A3E0:A9 C0 LDA #$C0
$A3E2:8D 7A 05 STA $057A = #$00
$A3E5:D0 0B BNE $A3F2
$A3E7:EE 75 05 INC $0575 = #$00
$A3EA:D0 06 BNE $A3F2
$A3EC:29 0F AND #$0F
$A3F0:84 ED STY $ED = #$00
$A3F2:A9 00 LDA #$00
$A3F4:8D 78 05 STA $0578 = #$02
$A3F7:20 49 A5 JSR $A549
When the tutorial is advising us to seek an appropriate offset to insert our "hijack", or "Jump to SubRoutine" (JSR), we first need to find some free space within the ROM banks that are currently loaded into RAM.
When we're being warned that it can't be RAM, it's because we can't run code through actual RAM addresses. Specifically, $0000 - $7FFF. If you hover the mouse over the vertical left bar, no information shows up, because these addresses aren't ROM banks.
Here are the ROM bank ranges currently loaded into RAM with the debugger frozen at this breakpoint:
$8000 - $9FFF = 0x3C010 - 0x3E00F
$A000 - $BFFF = 0x10010 - 0x1200F
$C000 - $DFFF = 0x00010 - 0x0200F
$E000 - $FFFF = 0x3E010 - 0x4000F
Looking through those ranges, I found some "FF" bytes at $FF2A (0x3FF3A). I saw in the tutorial that $E960 was used as an example for how to write the JSR, as the 60 is actually written first before the E9, and I saw that there are some "FF" bytes there, but in my case, I just chose to go with $FF2A.
By left-clicking in the debugger at $FF2A, the hex editor opens at 0x3FF3A, where I type in the code as directed by the tutorial.
0x3FF3A: A9 04 8D ED 00 60
As it is written if viewed in the debugger:
$FF2A:A9 04 LDA #$04
$FF2C:8D ED 00 STA $00ED = #$00
Next, we need to also add the JSR to $FF2A at the point where the debugger is frozen, back at $A3F0. Since there are multiple branches to $A3F2 (BNE) that I don't want to have to mess with, I decide that the 3 bytes I'll use for the JSR will be $A3EF - $A3F1.
So, at 0x103FF, I replace "88 84 ED" with "20 2A FF".
0x103FF: 20 2A FF
If I click Run, the debugger will actually freeze at $FF2C because we're writing to $00ED there now.
Now, I delete the write breakpoint so that I can play the game and test out the new code.
Turns out, however, that this code doesn't just hijack the Mushroom power-up, but even the Fire Flower and Leaf. After getting hit, the Frog Suit will still remain. Even after dying in a level by falling in a pit or lava, the Frog Suit still remains back on the World Map too.
We could always potentially look into how the original code determines which power-up to deal with, and modify the code (or whatever we find) so that the Mushroom truly acts as the Frog Suit, but everything else still behaves normally, and not just have the code overwrite everything by always using the Frog Suit.
What I decided to do in 1-1 was set a write breakpoint to $0578, and then I collected a Mushroom. You'll remember this address from $A3CA, where the game loads a value of #$02, meaning it has to be stored to $0578 at some point.
The debugger froze at $A8A0. This time, $A8A0 was 0x28B0 in the ROM. I scrolled up a line in the debugger to see the line of code that loads the value of #$02 and stores it to $0578 in the next.
$A89E:A9 02 LDA #$02
$A8A0:8D 78 05 STA $0578 = #$00
So, as a test, I went to 0x28AF in the ROM and changed 02 to 04, the value of the Frog Suit. I turned off the write breakpoint, clicked Run, and reset the game so that I could go to collect the Mushroom again.
The Mushroom power-up behaved as a Leaf. So, I had to go back in the debugger, add the write breakpoint to $0578 again, and then try again so that I could follow through the code to see what was going on.
This time, I hit the Mushroom block, dropped to the ground, and set both read and write breakpoints to $0578. I ran through until it froze at $A3CA (0x103DA). At this point, using "Step Into" showed the A, X and Y register values at each line of code to see exactly how each were changing.
$A3CA:AD 78 05 LDA $0578 = #$02 A:90 X:01 Y:FC
$A3CD:F0 2B BEQ $A3FA A:04 X:01 Y:FC
$A3CF:C9 0F CMP #$0F A:04 X:01 Y:FC
$A3D1:30 19 BMI $A3EC A:04 X:01 Y:FC
$A3EC:29 0F AND #$0F A:04 X:01 Y:FC
$A3EE:A8 TAY A:04 X:01 Y:FC
$A3EF:88 DEY A:04 X:01 Y:04
$A3F0:84 ED STY $ED = #$00 A:04 X:01 Y:03
$A3F2:A9 00 LDA #$00
$A3F4:8D 78 05 STA $0578 = #$02
$A3F7:20 49 A5 JSR $A549
So, due to the DEY, 04 actually became 03. So, the value loaded from $0578 is actually decreased by 01 before being stored into $00ED. Fire Flower has the value of 02, but 02 is stored into $0578 before 01 is stored into $00ED for the Mushroom. So, 05 should be decreased to 04 for the Frog Suit.
I guess it wasn't as complicated as it seemed, though this is part of the trial and error process that you shouldn't feel bad about working through, no matter how simple something might turn out to be, even if it seems confusing or complicated at first.
I went to 0x28AF in the ROM and changed the value to 05 instead, and went back to play through the game. Collecting the Mushroom turned Mario into Frog Mario. After getting hit, Frog Mario became Big Mario. After collecting a Leaf, Big Mario became Raccoon Mario. Getting hit twice, Raccoon Mario became Big Mario, and then Small Mario. After collecting another Mushroom, Mario became Frog Mario again.
So, at the end of all of this, it turns out the tutorial is just a means of testing out the fundamental basics of adding in a jump to custom code to do something different. The specific example of forcing the Frog Suit provided through the tutorial isn't necessarily practical in terms of SMB3 at its core, because you most likely wouldn't want a player to always be granted the Frog Suit, whether they're collecting any power-up or getting hit. However, it prompted us to look even deeper beyond where we were directed, and we ended up learning how a couple more things in SMB3 work, and implementing a more practical solution (disregarding how the green palette is applied and the transformation is still as though Mario is becoming Big Mario, so further hacking would be required to make the transformation seem more like the Frog Mario transformation) for the intended objective.Finally, at the end of all this, and after working through this whole process...
I just have to say, please don't feel so bad and down on yourself. You're learning. We all do. There's no way that you're terrible.
Here's another quick example that you could try - implementing infinite lives in SMB3.
If the RAM address isn't known, then you have to find it first by trial and error methods. However, thanks to the SMB3 RAM Map on Data Crystal
, we can discover that the RAM address for Mario's lives is $0736, and the RAM address for Luigi's lives is $0737.
In the case that we didn't, however, we could find those addresses by clicking in the Tools menu and selecting Cheats. Click Reset, and the values in RAM are displayed. Since Mario and Luigi start with 04 lives, try a Known Value of 04. The possibilities are reduced and narrowed down. Next, play through the game, lose a life, and then open the Cheats window again. The values on the left side are the original values of 04 from before, and the values on the right side are the updated/changed values. Only one value decreased to 03 - $0736 - and in most cases, we can conclude that it's the correct RAM address that we're looking for. In some cases, further testing to confirm this might be necessary. You would have to repeat the process for Luigi to find where his lives are stored. However, some games store common variables closely together in RAM, so you can sometimes find one and make an educated guess as to where the second player's lives are, like in this case for SMB3.
So, for testing purposes, we'll need a 2-player game to make sure that the infinite lives work for both players.
Set write breakpoints to the RAM addresses for both players' lives. In this case, set write breakpoints to both $0736 and $0737. Play through the game as Mario and lose a life. The debugger freezes at $9123, or 0x3D133 in the ROM:
$9123:DE 36 07 DEC $0736,X @ $0736 = #$04
See the X in "DEC $0736,X"? That means that, depending on what the value of the X register is, an address following $0736 has the potential to be decreased too, i.e. $0737, Luigi's lives.
By clicking Run, the debugger doesn't freeze again. Only one write to $0736.
Play through the game as Luigi and lose a life. The debugger freezes at $9123 again.
$9123:DE 36 07 DEC $0736,X @ $0737 = #$04
X is 01 this time. So, this one line of code - these 3 bytes - decrease the lives counters for both players whenever they lose lives.
Remember the NOP Opcode that the tutorial from earlier referred to? It's EA. Basically, it does nothing. So, go to 0x3D133 in the ROM, and replace "DE 36 07" with "EA EA EA". With the write breakpoints still active, click Run and play through the game with this change implemented. The debugger won't freeze again because the code that wrote to it is gone now, and the lives counters can't decrease, meaning that Mario and Luigi have infinite lives!
Now, the lives counters can still potentially increase
by collecting 1-UPs, collecting coins, collecting 3 cards at the ends of levels, playing the spade games, etc. unless you decide to find all of the cases of code that will increase these counters and NOP them out too, but regardless of that, they can never reach 0!
Hopefully this helps somewhat in getting a little more insight to Assembly, Opcodes, and the debugger and other tools of the FCEUX/FCEUXD emulator(s) series.
And remember - you can do it!