Rare's "Stop-n-swop" technique on NES

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Rare's "Stop-n-swop" technique on NES
by on (#5522)
Taking a cue from Rare's rumored "stop-n-swop" cartridge system for N64, where you swap game cartridges while the system is powered, I peformed the CIC disabling modification on my NES. I had the idea to copy my RS-232-based loader into low-memory from my devcart, eject the cart with the power still on, then insert a normal game cartridge, for the purpose of getting access to its mapper, etc.

The idea works (though it takes several tries until I get the game in without my code crashing as I insert it, much to the dismay of my NES cartridge connector). I've been able to dump several MMC1 and UNROM games and get a checksum match with dumps on the net, save and restore battery RAM to my PC, and do some reverse-engineering of the MMC5 in Castlevania 3. I plan on doing more reverse-engineering of the MMC1, MMC3, and MMC5. It would be neat to figure out how to enable MMC5 sound (I see several empty pads on my CV3 board for various resistors, perhaps necessary for digital-to-analog conversion).

I was also able to dump my US Game Genie ROM (it matches what's on the main nesdev page). I should also be able to access its custom hardware for reverse-engineering, in case anyone's interested.

The final thing I want to try is restoring a complete snapshot from my emulator into the actual game. This will be tricky since the code will ultimately have to erase itself. I'm hoping I can find half a page of bytes in a given snapshot that all have the same value. Combined with the movie replayer I made a few days ago, this could allow recording of movies on an emulator and playback on a real NES from any point, not just the beginning. Since my SNES devcart is almost the same, I was thinking of trying something similar on the SNES.

This might help me bootstrap to a better devcart. I was thinking of making one with flash RAM where the NES does the writing itself, using the serial interface with the PC. With this I could program the initial devcart, and easily program more for other people.

All this started with Zelda board with a single trace rerouted so that the battery RAM is selected in place of the ROM... :)

by on (#5531)
blargg,
do you have a machine language monitor for your RS-232 based comm?

Are you doing RS-232 in software, or using a UART?

by on (#5535)
I haven't written any kind of monitor yet. The only resident code is the loader, which I usually jump to at the end of any test code so it's ready to re-load. It's so easy to run new code that I just keep tweaking the asm code and re-running it. I was thinking of writing a small driver that allows the PC to request memory reads and writes, to allow interactive testing.

As for serial, it's in software running at 57.6 kbps. The only hardware is a MAX-232 level-shifter connected to the second joypad port, using the strobe for send and D0 to receive. It uses a simple checksum when loading code, and it hasn't ever gotten a bad checksum since I got it working over a year ago.

I've done more dumps and tests and it usually doesn't crash when swapping most cartridges. I wrote a small program that generates a tone with $4011 until you press a button on the joypad, to tell if it's crashed while swapping. I've been able to put a cartridge in and jiggle it, pop it up and down, etc. without it crashing.

This technique should work with any kind of devcart, so it's a viable means of dumping any NES game without any elaborate copy hardware.

by on (#5538)
As a test for my UART, I ported Woz's Apple 1 monitor (256 bytes!).

Code:

;Wozniak Apple1 Monitor
;written by Wozniak in 1975

   .inesprg 1
   .ineschr 0
   .inesmir 1
   .inesmap 1

   .bank 1

   .org $FE00   ; Rom Start

;******* Hardware Variables ************

UART = $5400
UARTLCR = $5403
UARTLSR = $5405

;********* Zero Page Variables *********

TEMP = $54
XAML = $24
XAMH = $25
STL  = $26
STH  = $27
L    = $28
H    = $29
YSAV = $2A
MODE = $2B

;******** $200-$27F Text Buffer *********

IN = $0200

;******** Listing *************


Init:          SEI             ;Clear decimal arithmetic mode.
                CLD             ;Clear Interrupt disable bit.               
               LDX #$00
                LDY #$00
                LDA #$00

               STA $2000
               STA $2001

UARTInit:       
                LDA #$80
                STA UARTLCR

                LDA #$0C
                STA UART

                LDA #$00
                STA $5401

                LDA #$03
                STA UARTLCR

                LDA #$00
                STA $5401
      STA $5404

                LDY #$7F        ;Mask for DSP data direction register
                ;STY UART         ;Set it up.
                LDA #$A7        ;KBD and DSP control register mask.
                ;STA KBDCR       ;Enable interrupts, set CA1, CB1, for
                ;STA DSPCR       ;postitive edge sense/output mode.

NOTCR:         
                CMP #$08        ;"<-"?
                BEQ BACKSPACE   ;Yes.
                CMP #$1B        ;ESC?
                BEQ ESCAPE      ;Yes.
                INY             ;Advance text index.
                BPL NEXTCHAR    ;Auto ESC if > 127.

ESCAPE:         
                LDA #$5C        ;"\".
                JSR ECHO        ;Output it.

GETLINE:       
                LDA #$0D        ;CR.
                JSR ECHO   ;Output it.
                LDY #$01        ;Initiallize text index.

BACKSPACE:     
                DEY             ;Backup text index.
                BMI GETLINE     ;Beyond start of line, reinitialize
                ;LDA KBDCR       ;Key ready?

NEXTCHAR:       
                LDA UARTLSR
                AND #$01
                BEQ NEXTCHAR    ;Loop until ready.
                LDA UART         ;Load character. B7 shoul be '1'
           ;STA UART
                STA IN,Y        ;Add to text buffer.

                JSR ECHO        ;Display character.

                CMP #$0D        ;CR?
                BNE NOTCR       ;No.
                LDY #$FF        ;Reset text index.
                LDA #$00        ;For XAM mode.
                TAX             ;0->X.

SETSTOR:       
                ASL A           ;Leaves $7B if setting STOR mode.

SETMODE:       
                STA <MODE        ;$00 = XAM, $7B = STOR, $A3 = BLOK XAM

BLSKIP:         
                INY             ;Advance text index.

NEXTITEM:       
                LDA IN,Y        ;Get character.
                CMP #$0D        ;CR?
                BEQ GETLINE     ;Yes, done this line.
                CMP #$2E        ;"."?
                BCC BLSKIP      ;Skip delimiter.
                BEQ SETMODE     ;Set BLOCK XAM mode.
                CMP #$3A        ;":"?
                BEQ SETSTOR     ;Yes, set STOR mode.
                CMP #$52        ;"R"?
                BEQ RUN         ;Yes, run user program.
                STX <L           ;$00->L.
                STX <H           ;and H.
                STY <YSAV        ;Save Y for comparison.

NEXTHEX:       
                LDA IN,Y        ;Get character for hex test.
                EOR #$30        ;Map digits to $0-9.
                CMP #$0A        ;Digit?
                BCC DIG         ;Yes.
                ADC #$88        ;Map letter "A"-"F" to $FA-FF.
                CMP #$FA        ;Hex letter?
                BCC NOTHEX      ;No, character not hex.

DIG:           
                ASL A
                ASL A            ;Hex digit to MSD of A.
                ASL A           
                ASL A           
                LDX #$04        ;Shift count.

HEXSHIFT:       
                ASL A            ;Hex digit left, MSDB to carry.
                ROL <L           ;Rotate into LSD.
                ROL <H           ;Rotate into MSD's.
                DEX             ;Done 4 shifts?
                BNE HEXSHIFT    ;No, loop.
                INY             ;Advance text index.
                BNE NEXTHEX     ;Always taken. Check next character for hex.

NOTHEX:         
                CPY <YSAV        ;Check if L, H empty (no hex digits)
                BEQ ESCAPE      ;Yes, generate ESC sequence.
                BIT <MODE        ;Test MODE byte.
                BVC NOTSTOR     ;B6 = 0 for STOR, 1 for XAM and BLOCK XAM
                LDA <L           ;LSD's of hex data.
                STA [STL,X]     ;Store at current 'store index'.
                INC <STL         ;Increment store index.
                BNE NEXTITEM    ;Get next item. (no carry).
                INC <STH         ;Add carry to 'store index' high order.

TONEXTITEM:     
                JMP NEXTITEM    ;Get next command item.

RUN:           
                JMP [XAML]      ;Run at current XAM index.

NOTSTOR:       
                BMI XAMNEXT     ;B7 = 0 for XAM, 1 for BLOCK XAM.
                LDX #$02        ;Byte count.

SETADR:         
                LDA <STH,X      ;Copy hex data to
                STA <XAMH,X     ;'store index'.
                STA <$23,X      ;And to 'XAM index'.
                DEX             ;Next of 2 bytes.
                BNE SETADR      ;Loop unless X = 0.

NXTPRNT:       
                BNE PRDATA      ;NE means no address to print.
                LDA #$0D        ;CR.
                JSR ECHO        ;Output it.
                LDA <XAMH        ;'Examine index' high-order byte.
                JSR PRBYTE      ;Output it in hex format.
                LDA <XAML        ;Low-order 'examine index' byte.
                JSR PRBYTE      ;Output it in hex format.
                LDA #$3A        ;":".
                JSR ECHO        ;Output it.

PRDATA:         
                LDA #$20        ;Blank.
                JSR ECHO        ;Output it.
                LDA [XAML,X]    ;Get data byte at 'examine index'
                JSR PRBYTE      ;Output it in hex format.

XAMNEXT:       
                STX <MODE        ;0->MODE (XAM mode).
                LDA <XAML
                CMP <L           ;Comapre 'examine index' to hex data.
                LDA <XAMH       
                SBC <H
                BCS TONEXTITEM  ;Not less, so no more data to output.
                INC <XAML       
                BNE MOD8CHK     ;Increment 'examine index'.
                INC <XAMH

MOD8CHK:       
                LDA <XAML        ;Check low-order 'examine index' byte
                AND #$07        ;For MOD 8=0
                BPL NXTPRNT     ;Always taken.

PRBYTE:         
                PHA             ;Save A for LSD.
                LSR A
                LSR A
                LSR A            ;MSD to LSD position.
                LSR A
                JSR PRHEX       ;Output hex digit.
                PLA             ;Restore A.

PRHEX:         
                AND #$0F        ;Make LSD for hex print.
                ORA #$30        ;Add "0".
                CMP #$3A        ;Digit?
                BCC ECHO        ;Yes, output it.
                ADC #$06        ;Add offset for letter.
                ;BIT DSP         ;DA bit (B7) cleared yet?
                ;BMI ECHO        ;No, wait for display

ECHO:
                STA <TEMP
ECHOLOOP:       LDA UARTLSR         ;Output character. Sets DA.
                AND #$20
                BEQ ECHOLOOP
                LDA <TEMP
                STA UART
                CMP #$0D   ;if output is CR then
                BEQ LINEFEED   ;linefeed
                RTS             ;Return.

LINEFEED:       LDA UARTLSR
                AND #$20
                BEQ LINEFEED
                LDA #$0A
      STA UART
      LDA #$0D
      RTS

   .bank 1
   .org $FFFA

   .dw 0
   .dw Init
   .dw 0

;End


by on (#5552)
Ugh, flood! :) I see you changed the constants from APPLEsci to ASCII, so it'd work with a normal terminal.

I'm now testing the MMC3 IRQ behavior. One problem I encountered was finding a cartridge with IRQ vectors that point into RAM. I wasn't able to, so I used the Game Genie to alter the IRQ vector at $fffe and $ffff. That is working, and I'm now beginning the IRQ testing.

Since my code can run before the Game Genie loads, I figured out how to write to the Game Genie registers (trivial to determine in an emulator, just run the Game Genie ROM and enter various codes while looking at the writes to $8000-$ffff). I doubt there's any way to re-enable it once its ROM has been hidden, except perhaps by pulsing one of the lines (which I plan on trying, since my Game Genie has DIP chips rather than glop tops).

I'm having so much fun testing all this hardware which I thought I wouldn't have access to.