Data injection via Link Cable in Gen 1
Posted by: Aldrasio
Date: 2016-07-29 21:18:01
I designed a payload that allows you to dump an arbitrary amount of data to anywhere in the RAM, and then jump to it. I'm still playing with the possibilities, but the payload wouldn't need a whole lot of modification to work as an SRAM dumper. The payload is as follows:
F3 F0 44 FE 90 38 FA F0 40 E6 7F E0 40 1E 02 16
02 3E 81 E0 02 F0 02 E6 80 20 FA F0 01 15 28 03
4F 18 EE 47 C5 1D 20 E7 0E 80 21 10 C6 2A E2 0C
2A A7 0D 0A 20 FA C1 E1 E5 C3 80 FF 3E 81 E0 02
F0 02 E6 80 20 FA F0 01 22 0B 78 B1 20 EE C9
di
.displayloop
ld a, [$ff44]
cp $90
jr c, .displayloop
ld a, [$ff40]
and $7f
ld [$ff40], a
ld e, 2
.read2bytes
ld d, 2
.read
ld a, $81
ld [$ff02], a
.waitloop1
ld a, [$ff02]
and $80
jr nz, .waitloop1
ld a, [$ff01]
dec d
jr z, .next
ld c, a
jr .read
.next
ld b, a
push bc
dec e
jr nz, .read2bytes
ld c, $80
ld hl, .hramdata ; $C610
ld a, [hli]
.loop
ld [c], a
inc c
ld a, [hli]
and a
jr nz, .loop
pop bc
pop hl
push hl
jp $ff80
.hramdata
ld a, $81
ld [$ff02], a
.waitloop2
ld a, [$ff02]
and $80
jr nz, .waitloop2
ld a, [$ff01]
ld [hli], a
dec bc
ld a, b
or c
jr nz, .hramdata
ret
nop
A little sloppy, but I was going for brevity. The nop at the end is important.
The way this payload works is that once the initial buffer overflow is finished, it disables interrupts (since I don't have control over interrupt vectors) and then disables the screen (seemed like a good idea). Then it takes 2 sets of 2 bytes and pushes them to the stack. The first set of 2 bytes is the starting address for the next wave of data. The second set of 2 bytes is the length of the data. Both sets are little-endian.
Once it has those values, it copies everything from ".hramdata" to the nop to HRAM, where OAM code usually sits. Then it jumps to $ff80. Once it's at $ff80, it can start loading data from the serial port to any part of the RAM (except, in this case, SRAM. SRAM was not enabled as part of the payload). Once it's reached the end of the final payload, it jumps to the starting address from the beginning of the payload.
The main reason I went ahead and coded a different serial data receiver than the one on the cart is because the one on the cart is designed to run alongside the rest of the game, so it's going to be slower if you want to send a lot of data. With the barebones receiver coded here, you can send arbitrary data over more quickly, although the Gameboy will have no way of responding if anything goes wrong.
I haven't tried this on hardware, and I'm still playing with the possibilities, but as it stands you can load an arbitrarily-sized program into RAM with this method.