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.