This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Hacking adventures, anyone?

Hacking adventures, anyone?
by on (#77099)
Well, got a little curious my self when trying to help somebody in the right direction on NintendoAge with how to hack/change R.B.I. Baseball. Well, they got everything done basically but the title screen. I tried to do this part my self, just find a little routine and edit it a bit. But what I found wasn't as easy to explain:

Code:
00:AF9F:A0 00     LDY #$00
00:AFA1:B9 12 B0  LDA $B012,Y @ $B012 = #$14
00:AFA4:85 2B     STA $002B = #$14
00:AFA6:B9 13 B0  LDA $B013,Y @ $B013 = #$B0
00:AFA9:85 2C     STA $002C = #$B0
00:AFAB:A9 67     LDA #$67
00:AFAD:85 2D     STA $002D = #$67
00:AFAF:A9 20     LDA #$20
00:AFB1:85 2E     STA $002E = #$20
00:AFB3:A9 0A     LDA #$0A
00:AFB5:85 2F     STA $002F = #$0A
00:AFB7:A5 2E     LDA $002E = #$20
00:AFB9:8D 06 20  STA $2006 = #$67
00:AFBC:A5 2D     LDA $002D = #$67
00:AFBE:8D 06 20  STA $2006 = #$67
00:AFC1:A0 00     LDY #$00
00:AFC3:B1 2B     LDA ($2B),Y @ $B014 = #$6E
00:AFC5:8D 07 20  STA $2007 = #$00
00:AFC8:C8        INY
00:AFC9:C0 13     CPY #$13
00:AFCB:D0 F6     BNE $AFC3
00:AFCD:A9 13     LDA #$13
00:AFCF:18        CLC
00:AFD0:65 2B     ADC $002B = #$14
00:AFD2:85 2B     STA $002B = #$14
00:AFD4:A9 00     LDA #$00
00:AFD6:65 2C     ADC $002C = #$B0
00:AFD8:85 2C     STA $002C = #$B0
00:AFDA:A9 20     LDA #$20
00:AFDC:18        CLC
00:AFDD:65 2D     ADC $002D = #$67
00:AFDF:85 2D     STA $002D = #$67
00:AFE1:A9 00     LDA #$00
00:AFE3:65 2E     ADC $002E = #$20
00:AFE5:85 2E     STA $002E = #$20
00:AFE7:C6 2F     DEC $002F = #$0A
00:AFE9:D0 CC     BNE $AFB7
00:AFEB:20 4A B4  JSR $B44A
.....
00:B44A:68        PLA
00:B44B:85 2B     STA $002B = #$14
00:B44D:68        PLA
00:B44E:85 2C     STA $002C = #$B0
00:B450:A0 01     LDY #$01
00:B452:AE 02 20  LDX $2002 = #$07
00:B455:C8        INY
00:B456:B1 2B     LDA ($2B),Y @ $B014 = #$6E
00:B458:8D 06 20  STA $2006 = #$67
00:B45B:88        DEY
00:B45C:B1 2B     LDA ($2B),Y @ $B014 = #$6E
00:B45E:8D 06 20  STA $2006 = #$67
00:B461:C8        INY
00:B462:C8        INY
00:B463:B1 2B     LDA ($2B),Y @ $B014 = #$6E
00:B465:C8        INY
00:B466:C9 FE     CMP #$FE
00:B468:B0 06     BCS $B470
00:B46A:8D 07 20  STA $2007 = #$00
00:B46D:4C 63 B4  JMP $B463
00:B470:C9 FF     CMP #$FF
00:B472:D0 E1     BNE $B455
00:B474:88        DEY
00:B475:98        TYA
00:B476:18        CLC
00:B477:65 2B     ADC $002B = #$14
00:B479:A8        TAY
00:B47A:A9 00     LDA #$00
00:B47C:65 2C     ADC $002C = #$B0
00:B47E:48        PHA
00:B47F:98        TYA
00:B480:48        PHA
00:B481:60        RTS


I have no idea where to even start on that routine just to put the title screen up. And there's even more code after the JSR! Anyone else run into this stuff [Bad code, anything of that matter.] trying to change something in a game?

by on (#77104)
I'll take this question because ROM hacking uses some of the same software archaeology skills that I use when fixing up an 11-year-old project or recovering half-lost source code.

The first half is straightforward, looking like copying a 19x10 tile area to the screen. Let me rewrite in pseudocode:
Code:
short srcAddress at $002B
short dstAddress at $002D
char numRowsLeft at $002F

# AF9F
srcAddress = b012Table[0]
dstAddress = $2067
for numRowsLeft in range(10, 0, -1):
    # AFC1
    for Y in range(0, $13):
        write srcAddress[Y] to PPUDATA
    # AFCD
    srcAddress += $13
    dstAddress += $20  # move to next row
    # AFE7


$AFEB looks like it uses the strategy of putting arguments to a subroutine after the JSR. MLI calls in ProDOS use this strategy; so do switch statements of Super Mario Bros. (Refer to SMBDis by doppelganger for an explanation of JumpEngine in SMB1.)

As I understand it, the subroutine at $B44A works like this (again, pseudocode):
Code:
short srcAddress at $002B

def writeStrings():
    # B44A
    Pop return address into srcAddress
    Read PPUSTATUS to reset the PPUADDR latch
    do:
        # B453
        Copy 2-byte address to PPUADDR
        # B463
        Copy bytes to PPUDATA until $FE or $FF value
    while terminator was $FE
    # B474
    Add the total number of bytes processed to srcAddress
    # B47E
    Push srcAddress back on the stack

And it's used like this:
Code:
  jsr writeStrings
  .addr $2104
  .byt "Hello World", $FE
  .addr $2124
  .byt "This is line 2", $FE
  .addr $2124
  .byt "This is the last line", $FF
  ; control returns here

by on (#77105)
Doh: Tepples beat me, but I'll still post this, I suppose. Here's my thought process.

The goal is simply to hack the title screen? I don't see any bad code here. Sometimes you don't hack code. Sometimes you hack data.

This routine puts the address of the titlescreen's data into $2B and $2C. It then loops loading values from that address using indexed indirect mode.

Since I don't know anything about bank switching, I set a breakpoint at AFC3, and stepped through it looking for the values loaded to A. (Which of course are then stored to $2007 which writes a tile.)

I got these: 70 70 70 70 70 7D 7E 80 C4

I then ran a search for those hex values in my hex editor, and changed them to 01 02 03 04 05 7D 7E 80 C4, and the title screen displayed those tiles, which happened to display 12345 at the very top of the B.

This lead me to believe that data for this title screen is uncompressed, as $70 is repeated many times and changing it changes exactly 1 tile.

The start of the data in the rom is at $3024 in the actual rom file with header. Changing values here chooses which tiles are drawn to the titlescreen.

The title screen that is not blank is $12 tiles long. (Which is why cmp #$13 ends the loop) So each set of $12 tiles starting at the address affects 1 row, starting 8 tiles from the right. This saves at least some space since the blank tiles before each row (24 24 24 24 24 24 24 24) don't have to be stored. That gets you as far as the main logo.

I have no idea what the second thing does, and definitely don't want to find out since there's no context, but hopefully this will get you started.

1P PLAY, 2P PLAY and WATCH, plus the copyright info seem to be affected by another routine. But... you don't even need to FIND that routine. You didn't need to look at the code at all. I just searched for the tile numbers the display 1 PLAY (01 19 24 19 15 0A 22) And changed them to whatever. I could have done exactly that for the main logo as well. Because the data is uncompressed, you don't really need a debugger at all. Is that all you want, or is there another goal in mind?

by on (#77111)
Nope, I was just trying to help the guy find the data in the ROM and since I didn't want to get into any guess and check algorithms and data types, I just waited for it to write to the name table. This was what I stumbled upon. For the purpose it is used by, it seems just a bit overly uneededly complex compared to most other code. Thanks for explaining how it works, now I'll relay this to the guy on NA. And before setting PPU Read/Write breaks, I found about 3 different subroutines that cleared the screen and other weird stuff, heh.


I don't see why it needs so much zero page and complexity to it. Like said above, per line, if you put it into RLE compression, the end blocks take up less space in the ROM and you get a shorter program. It's just.....not my choice I guess. Thanks for reading it through for me, it was at 5AM when I posted that just trying to find a simple fix for the guy. :)

by on (#77112)
3gengames wrote:
For the purpose it is used by, it seems just a bit overly uneededly complex compared to most other code.

The first half looks like the most straightforward way to write a rectangular chunk of nametable to the screen. The second half, which I called writeStrings, appears to have come from a library used by multiple games and/or multiple places within a game, and its complexity follows from its generality.

3gengames wrote:
I don't see why it needs so much zero page

On Atari 2600, five bytes is "a lot of zero page" because zero page is shared with the stack, the game state, and even the I/O registers. On Apple II, five bytes is "a lot of zero page" because ROM BASIC eats up most of it. On NES, neither is the case. In fact, my standard calling convention on the NES reserves 16 bytes for a subroutine's local variables.

by on (#77113)
Eh, okay then. Not the way I would have done it, but if you think it's fine then so be it.

by on (#77118)
3gengames wrote:
Eh, okay then. Not the way I would have done it, but if you think it's fine then so be it.

I don't think this is a matter of whether people think it's fine, it's just that this is what the game used. You could make a simpler routine if you wanted, but hacking the routine itself would be a lot more trouble than just hacking the data, don't you think?
Re: Hacking adventures, anyone?
by on (#77119)
I hacked RBI to contain all the baseball teams for someone at dee-nee.

Not sure what you are trying to do with the code but $AF9F is the code to draw the Baseball at the beginning of the game and $B44A is one of RBI's codes to write data to the PPU. RBI tends to have PPU data inside the code or uses a pointer at $3C with routines, like at $B44A, to write the data.

by on (#77129)
Yeah, guys, I am not hacking the routine. I used it to find the data in the ROM [Well, was going to use it for that.] I just thought this one was strange and bloated and would of thought it would have made a good topic. I guess not.