Space Invaders

What people usually understand about how Space Invaders works is that a ton of data is sent to SNES RAM via DATA_TRN, then a JUMP command is issued to that area so that, essentially, you are playing a SNES game.

There are a couple other interesting details:

DATA_TRN patch

DATA_SND packets are used to setup a hook that runs before SGB packets are handled, and that hook completely replaces DATA_TRN so that it can be faster.

.org $a00

_PreExecPacketCmdHook:
; Only override DATA_TRN
    lda wCurrPacketCmd                           ; $0a00 : $ad, $c2, $02
    cmp #CMD_DATA_TRN                            ; $0a03 : $c9, $10
    bne @done                                    ; $0a05 : $d0, $4c

; Signal to GB that DATA_TRN is starting
    lda #$01                                     ; $0a07 : $a9, $01
    sta ICD2P_REGS.l                             ; $0a09 : $8f, $04, $60, $00

; Do normal DATA_TRN, starting with setting the dest addr of the vram data
    lda wSGBPacketsData+1                        ; $0a0d : $ad, $01, $06
    sta wDataSendDestAddr.b                      ; $0a10 : $85, $b0
    lda wSGBPacketsData+2                        ; $0a12 : $ad, $02, $06
    sta wDataSendDestAddr.b+1                    ; $0a15 : $85, $b1
    lda wSGBPacketsData+3                        ; $0a17 : $ad, $03, $06
    sta wDataSendDestAddr.b+2                    ; $0a1a : $85, $b2

; Replicate DMA transferring the GB screen, catering to SGB BIOS versions
    lda CART_VERSION.l                           ; $0a1c : $af, $db, $ff, $00
    beq @ver0                                    ; $0a20 : $f0, $05

    jsr DmaTransferAGBScreen_nonVer0             ; $0a22 : $20, $8d, $c5
    bra +                                        ; $0a25 : $80, $03

@ver0:
    jsr DmaTransferAGBScreen_ver0                ; $0a27 : $20, $90, $c5

; Signal to GB that we've loaded the screen, so it can load new data, while
; we're doing the mem copy below
+   lda #$00                                     ; $0a2a : $a9, $00
    sta ICD2P_REGS.l                             ; $0a2c : $8f, $04, $60, $00

; Set the GB screen's ram buffer as the src pointer
    lda wCurrPtrGBTileDataBuffer                 ; $0a30 : $ad, $84, $02
    sta wGBTileDataRamSrc.b                      ; $0a33 : $85, $98
    lda wCurrPtrGBTileDataBuffer+1               ; $0a35 : $ad, $85, $02
    sta wGBTileDataRamSrc.b+1                    ; $0a38 : $85, $99
    lda #:wGBTileData0.b                         ; $0a3a : $a9, $7e
    sta wGBTileDataRamSrc.b+2                    ; $0a3c : $85, $9a

; Copy over the $1000 screen bytes
    setaxy16                                     ; $0a3e : $c2, $30
    ldx #$0800                                   ; $0a40 : $a2, $00, $08
    ldy #$0000                                   ; $0a43 : $a0, $00, $00

@nextWord:
    lda [wGBTileDataRamSrc], Y                   ; $0a46 : $b7, $98
    sta [wDataSendDestAddr], Y                   ; $0a48 : $97, $b0
    iny                                          ; $0a4a : $c8
    iny                                          ; $0a4b : $c8
    dex                                          ; $0a4c : $ca
    bne @nextWord                                ; $0a4d : $d0, $f7

; Skip doing the original DATA_TRN
    setaxy8                                      ; $0a4f : $e2, $30
    pla                                          ; $0a51 : $68
    pla                                          ; $0a52 : $68

@done:
    rts                                          ; $0a53 : $60

In short:

  • In the replacement DATA_TRN, $01 is sent to GB's player 1 input
  • The GB screen's data is copied to a generic ram buffer, due to using the generic routine DmaTransferAGBScreen
  • After the GB's screen data is loaded in, $00 is sent to GB's player 1 input, which will signal it to do whatever it wants, for example, load a new screen for the next DATA_TRN
  • While GB is loading a new screen, SNES is memcopying from the generic screen data buffer to the actual desired DATA_TRN destination

From the GB side, those values go through a PollInput routine, whose normal function cpl and swap the values to be $ef and $ff respectively:

DataTrn1000hBankBytes:

...

; The DATA_TRN patch will send 1 when DATA_TRN will load the VRAM tile data...
:   call PollInput                               ; $32f4 : $cd, $6c, $10
    ld a, [wBtnsHeld]                            ; $32f7 : $fa, $51, $d7
    cp $ef                                       ; $32fa : $fe, $ef
    jr nz, :-                                    ; $32fc : $20, $f6

; Then 0 once that vram tile data has all been read
:   call PollInput                               ; $32fe : $cd, $6c, $10
    ld a, [wBtnsHeld]                            ; $3301 : $fa, $51, $d7
    cp $ff                                       ; $3304 : $fe, $ff
    jr nz, :-                                    ; $3306 : $20, $f6

GB is still active

The GB is still running even after a JUMP packet is issued. The data needed for the SNES game to fully function is larger than the free ram areas of bank $7e and $7f. The new sound engine, for example, and all of its data, take up around $cb00 bytes in total. So the GB will DATA_TRN in the background as needed by the SNES game, dependent on special values that are sent to player 1's input.

HandleArcadeMode:

...

.mainLoop:
; Get buttons sent by SNES
    call PollInput                               ; $320c : $cd, $6c, $10
    ld a, [wLastSnesBtnsHeld]                    ; $320f : $fa, $67, $d7
    ld b, a                                      ; $3212 : $47
    ld a, [wBtnsHeld]                            ; $3213 : $fa, $51, $d7

; Wait until a new code has been sent
    cp b                                         ; $3216 : $b8
    jr z, .mainLoop                              ; $3217 : $28, $f3

; Check if SNES sends $3f
    cp $0c                                       ; $3219 : $fe, $0c
    jr z, .code3Fh                               ; $321b : $28, $1c

; Check if SNES sends $2f
    ld hl, DataTrnBanks_3                        ; $321d : $21, $4e, $33
    cp $0d                                       ; $3220 : $fe, $0d
    jr z, .code1FhOr2Fh                          ; $3222 : $28, $07

; Check if SNES sends $1f
    ld hl, DataTrnBanks_2                        ; $3224 : $21, $4a, $33
    cp $0e                                       ; $3227 : $fe, $0e
    jr nz, .mainLoop                             ; $3229 : $20, $e1

.code1FhOr2Fh:
    ld [wLastSnesBtnsHeld], a                    ; $322b : $ea, $67, $d7

; DATA_TRN banks in HL, based on if $1f or $2f sent
    call DataTrnSomeBanks                        ; $322e : $cd, $4a, $32

; 7f:2006 jumps to the main handler for the SNES game, past some init code
    ld hl, Packet_JUMP_7f2006h                   ; $3231 : $21, $c0, $41
    call SendSGBPacketBank3                      ; $3234 : $cd, $a8, $0e
    jr .mainLoop                                 ; $3237 : $18, $d3

.code3Fh:
    ld [wLastSnesBtnsHeld], a                    ; $3239 : $ea, $67, $d7

; DATA_TRN banks in DataTrnBanks_1
    ld hl, DataTrnBanks_1                        ; $323c : $21, $43, $33
    call DataTrnSomeBanks                        ; $323f : $cd, $4a, $32

; 7f:2000 jumps to the main handler for the SNES game
    ld hl, Packet_JUMP_7f2000h                   ; $3242 : $21, $b0, $41
    call SendSGBPacketBank3                      ; $3245 : $cd, $a8, $0e
    jr .mainLoop                                 ; $3248 : $18, $c2

There is a DataTrnBanks_0 sent before this main loop. This loads in Space Invaders' sound engine and data, then sends the code $3f so that DataTrnBanks_1 can be loaded in.