In the last lesson, we isolated the Trevor's horizontal and vertical movement codes. We will pick up where we left off -- isolating changes in address $0520.
Disable all breakpoints and run the game so Trevor finishes his jump. Change the $041C ECRW- breakpoint to instead watch $0520. Delete the conditions, since they won't apply to $0520. Enable the breakpoint and make Trevor jump. The debugger immediately shows the following result:
0E:98E6:9D 20 05 STA $0520,X @ $0520 = #$00
0E:98E9:FE D8 05 INC $05D8,X @ $05D8 = #$09
0E:98EC:60 RTS
That's a bit short and doesn't tell us much. Well, not yet. First, look at the registers. The Accumulator is set to #$FB, X is #$00 and Y is #$09.
First, let's analyze the Accumulator. Its value is greater than #$80. The Negative bit in the status register isn't set currently, but we know that #$FB is either 251 or -5, depending on whether it's handled as an unsigned or signed byte. However, even if it's treated as an unsigned byte, the Negative bit should still be set. It's not, so that means something important happened just prior to $98E6 was handled.
There is another clue that something significant just occurred. The Y register is set. We can expect the X register to be set (even though here it isn't) because that typically handles RAM offsets for instances (Trevor is X:#$00, enemy 1 is X:#$01, enemy 2 is X:#$02, and so on). The Y register is typically only set when temporarily housing accumulator values or handling offsets in the PRG-ROM.
We need to see more of the PRG-ROM if we want to find any significant code that lead up to this breakpoint. Scrolling up through the debugger to the next RTS call reveals the following:
0E:98C6:AD 65 05 LDA $0565 = #$08
0E:98C9:C9 26 CMP #$26
0E:98CB:F0 CC BEQ $9899
0E:98CD:A2 00 LDX #$00
0E:98CF:A9 00 LDA #$00
0E:98D1:9D 37 05 STA $0537,X @ $0537 = #$00
0E:98D4:9D DB 04 STA $04DB,X @ $04DB = #$00
0E:98D7:BC D8 05 LDY $05D8,X @ $05D8 = #$09
0E:98DA:BD C1 05 LDA $05C1,X @ $05C1 = #$00
0E:98DD:D0 1C BNE $98FB
0E:98DF:B9 7B 98 LDA $987B,Y @ $9884 = #$FB
0E:98E2:C9 81 CMP #$81
0E:98E4:F0 07 BEQ $98ED
0E:98E6:9D 20 05 STA $0520,X @ $0520 = #$00
0E:98E9:FE D8 05 INC $05D8,X @ $05D8 = #$09
0E:98EC:60 RTS
(NOTE: If you get a code that instead contains the routine STA $006B, it means you are in a vertical room. Move to a horizontal room and then try again.)
The first few lines is a simple comparison checking if $0565=#$26. We won't worry about it here, but this checks if Trevor's sprite is his knocked-back sprite (for when he's wounded). The next few lines simply set X to refer to Trevor's offsets and then initialize a few RAM addresses.
The Y register gets set to the value of $05D8. For now it's an obscure byte that gets increased at the end of this subroutine. You'll eventually find out it's the fall height counter used to determine if Trevor lands nimbly or awkwardly (characterized by a momentary stun upon landing).
The next line checks an unknown address, $05C1, and skips the rest of this code if the byte has been set. This is actually a very important address and we will return to it later. Anything that causes the game to skip an entire segment of code is probably worth looking into.
Now we're where we want to be. Notice the next line refers to an address located in the PRG-ROM with the Y register as an offset. We still don't know why Y is set to 9, but that's not important right now. These types of calls tell us a lot of relevant information beyond the scope of our current task, so let's analyze the one at hand.
LDA $987B,Y @ $9884 = #$FB
This code sets the Accumulator to the value at $9884 in the current bank, which is #$FB. This means $9884 doesn't actually contain any routine calls, it just stores a constant. How many other addresses around it store constants? According to this line, the list of this particular class of constants starts at $987B. The next line in the routine checks if the value loaded into the Accumulator is #$81, so we should look at the PRG-ROM range starting from $987B for any address with the value #$81.
0E:987B:80 UNDEFINED
0E:987C:FA UNDEFINED
0E:987D:FA UNDEFINED
0E:987E:FA UNDEFINED
0E:987F:FA UNDEFINED
0E:9880:FA UNDEFINED
0E:9881:FB UNDEFINED
0E:9882:FB UNDEFINED
0E:9883:FB UNDEFINED
0E:9884:FB UNDEFINED
0E:9885:FB UNDEFINED
0E:9886:FD FD FD SBC $FDFD,X @ $FDFD = #$B1
0E:9889:FD FD FE SBC $FEFD,X @ $FEFD = #$B6
0E:988C:FE FE FF INC $FFFE,X @ $FFFE = #$F5
0E:988F:FF UNDEFINED
0E:9890:FF UNDEFINED
0E:9891:FF UNDEFINED
0E:9892:00 BRK
0E:9893:FF UNDEFINED
0E:9894:00 BRK
0E:9895:00 BRK
0E:9896:00 BRK
0E:9897:00 BRK
0E:9898:81
Whew! That's a big list and much of it is just repeated values. You're about to learn why I've called some of Konami's coding very inefficient.
If you actually measure out Trevor's movement in the game when he jumps, you'll discover his y-coordinate changes by -5, then -3, then -2, then -1, then 0, then -1, then 0 again, then it essentially reverses. Looking at $9884, we in fact see that same pattern. We've just discovered Trevor's vspeed for when he jumps. You'll notice there is no gravity or complex trigonometric function to calculate his vspeed. It's just a simple array of constants taking up a big (relatively speaking) chunk of memory.
There is one more useful tidbit we can garner from this section of code. Hold your mouse over the empty grey bar on the far left side of the Debugger near $987C. At the bottom you will see the word "Offset". The large hexadecimal number after that is the identical ROM address. In other words, if you want to permanently change Trevor's jumping pattern, that is the address in the ROM you would need to edit (but don't edit $987B or $9898, as the game uses those as specific markers).
The fall height gets increased at the end of the subroutine, which we can surmise will be applied to the Y register in order to increase the PRG-ROM offset for Trevor's vspeed. Click Run to skip ahead to the next reference of $0520. It's the vertical movement subroutine. We've already dealt with that, so click Run once again. As expected, it's the code we just analyzed. So now we know how Trevor jumps.
But what about that address we neglected earlier, $05C1? Let Trevor finish his jump routine, then change the breakpoint to watch for $05C1. Now make Trevor jump again.
0E:9624:A9 08 LDA #$08
0E:9626:8D 65 05 STA $0565 = #$08
0E:9629:A5 2A LDA $002A = #$80
0E:962B:85 10 STA $0010 = #$80
0E:962D:A2 00 LDX #$00
0E:962F:A9 16 LDA #$16
0E:9631:9D 00 04 STA $0400,X @ $0400 = #$16
0E:9634:A9 00 LDA #$00
0E:9636:9D C1 05 STA $05C1,X @ $05C1 = #$01
0E:9639:A9 09 LDA #$09
0E:963B:9D D8 05 STA $05D8,X @ $05D8 = #$07
0E:963E:A5 10 LDA $0010 = #$80
0E:9640:4A LSR
0E:9641:B0 08 BCS $964B
0E:9643:4A LSR
0E:9644:B0 10 BCS $9656
0E:9646:A9 00 LDA #$00
0E:9648:A8 TAY
0E:9649:F0 14 BEQ $965F
0E:964B:A9 00 LDA #$00
0E:964D:9D A8 04 STA $04A8,X @ $04A8 = #$00
0E:9650:A9 01 LDA #$01
0E:9652:A0 00 LDY #$00
0E:9654:F0 09 BEQ $965F
0E:9656:A9 01 LDA #$01
0E:9658:9D A8 04 STA $04A8,X @ $04A8 = #$00
0E:965B:A9 FF LDA #$FF
0E:965D:A0 00 LDY #$00
0E:965F:9D F2 04 STA $04F2,X @ $04F2 = #$00
0E:9662:98 TYA
0E:9663:9D 09 05 STA $0509,X @ $0509 = #$00
0E:9666:60 RTS
I went ahead and scrolled up into the earlier code for education's sake. At $9636 the game clears $05C1. The next line sets $05D8, which I said was the fall height, to #$09.
The next line loads the value of $0010 into the Accumulator. That's a kind of Zero Page reference I call Hacker Hell. Some addresses in the Zero Page store useful values, such as timers, hearts, score, and whatnot. Byte $0010 is not one of those useful addresses. It's still a useful address, just not out of context. No matter where you are in the PRG-ROM, some addresses in the Zero Page will hold the same value, but not this one. When dealing with the Zero Page, it's helpful to have a notepad handy so you can keep track of which values are loaded into it. Sometimes a PRG-ROM offset is loaded into the Zero Page, while other times it's something specific like the current instance's bounding box.
Back on track, we know what happens when $05C1 is #$00. We want to know when it changes and what happens in such cases. Change the breakpoint to $05C1 EC-W- and then run the game until the next breakpoint. Eventually you should reach this point:
0E:98ED:A9 00 LDA #$00
0E:98EF:9D 20 05 STA $0520,X @ $0520 = #$00
0E:98F2:DE D8 05 DEC $05D8,X @ $05D8 = #$1C
0E:98F5:A9 01 LDA #$01
0E:98F7:9D C1 05 STA $05C1,X @ $05C1 = #$00
0E:98FA:60 RTS
Short, sweet, and to the point. This is a great subroutine! Wait a second, though. Some of it looks a little familiar. Once again we're amending $05D8, but whereas last time the code increased it, this time it decreased it. Also, the address at the start of the subroutine looks familiar. If we scroll up a little farther, we'll find the branch call that sent us to this subroutine:
0E:98DF:B9 7B 98 LDA $987B,Y @ $9898 = #$81
0E:98E2:C9 81 CMP #$81
0E:98E4:F0 07 BEQ $98ED
That's the branching conditional from our very first code when we were tracing $0520! Now we know what the purpose of that #$81 was -- to set $05C1. Remember that first routine branched out if $05C1 was set, so change the breakpoint back to $05C1 ECRW- and click Run to jump straight to said conditional. Click Step Into a couple times to follow the branch.
0E:98FB:B9 7B 98 LDA $987B,Y @ $9897 = #$00
0E:98FE:C9 80 CMP #$80
0E:9900:F0 0C BEQ $990E
0E:9902:DE D8 05 DEC $05D8,X @ $05D8 = #$1C
0E:9905:49 FF EOR #$FF
0E:9907:18 CLC
0E:9908:69 01 ADC #$01
0E:990A:9D 20 05 STA $0520,X @ $0520 = #$00
0E:990D:60 RTS
This is a nice little subroutine for learning. We have our vspeed reference offset first. This time it branches on #$80. Looking back at Trevor's vspeed list, that's at $987B, or when $05D8 equals #$00. Said variable gets decreased, so we know eventually it will indeed likely reach #$00.
Now Konami does something fun. Well, it's fun if you don't know squat about working with bytes. They take Trevor's vspeed and EOR it with $FF. In other words, they invert it. Since his vspeed according to that array is always negative, this makes it positive. However, the result is 1 off from what you'd expect -- #$FB^#$FF does not equal #$05, but #$04. So they add #$01. This is how you retrieve the absolute value of a signed byte, but in this case it's just a straight inversion of vspeed. (Learning how to find the absolute value, though, helped immensely in cracking collision subroutines).
That's it for today's lesson.