Updating payload and accounting for payload waiting

This commit is contained in:
GearsProgress
2026-05-17 14:57:44 -04:00
parent eeb14a8b2a
commit e4db1d75c8
9 changed files with 609 additions and 26 deletions

View File

@@ -34,6 +34,7 @@ enum CompositeState
enum SubstateState
{
NO_SUBSTATE,
// INITIAL_CONNECTION
CLOCK,
SAVE_SUCCESS,
@@ -42,8 +43,9 @@ enum SubstateState
WAIT_FOR_TRADE,
TRADE_PREAMBLE,
TRADE,
EXTRA_BYTE,
MAIL,
WAIT_FOR_PAYLOAD,
GET_CHECKSUM,
END,
};

View File

@@ -91,6 +91,7 @@ void LinkConnection::startConnection(CompositeState startState)
void LinkConnection::exchangeBytes()
{
/*
int timeout_frames = 10;
inData = linkSPI->transfer(outData, [&timeout_frames]()
{
@@ -98,7 +99,7 @@ void LinkConnection::exchangeBytes()
// Waiting here prevents valid bytes from being reported as timeouts.
global_next_frame();
return --timeout_frames <= 0; });
*/
inData = linkSPI->transfer(outData);
}
@@ -281,24 +282,42 @@ void LinkConnection::logicState_initConnection()
}
else
{
subState = EXTRA_BYTE;
subState = WAIT_FOR_PAYLOAD;
}
}
outData = curr_payload[subStateCounter];
break;
case EXTRA_BYTE:
if (subStateCounter > 1000) // This value just waits for the text to be printed. Should probably be dynamic.
{
subState = END;
}
outData = 0x00;
break;
case MAIL:
if (subStateCounter >= 0x186)
{
subState = WAIT_FOR_PAYLOAD;
}
break;
case WAIT_FOR_PAYLOAD:
if (inData == 0xFD)
{
subState = GET_CHECKSUM;
}
outData = 0x00;
break;
case GET_CHECKSUM:
if (inData != 0xFD)
{
outData = 0xFD;
}
else if (subStateCounter >= 10)
{
subState = END;
}
else
{
outData = 0x01;
}
break;
case END:

View File

@@ -178,9 +178,9 @@ void Pokemon_Party::start_link()
load_localized_charset(debug_charset, 3, ENGLISH);
globalLinkCable.setup(debug_charset);
globalLinkCable.startConnection(INITIAL_CONNECTION);
while (globalLinkCable.compState == INITIAL_CONNECTION)
while (/*globalLinkCable.compState == INITIAL_CONNECTION*/ true)
{
VBlankIntrWait();
//VBlankIntrWait();
};
box.loadData(curr_gb_rom.generation, (Language)curr_gb_rom.language, box_data_array);

View File

@@ -0,0 +1,85 @@
Summary
The specific payload exchanges packets with PTGB.
PTGB can influence the behavior of the GB by sending packets with specific commands.
The GB can read data and send it to PTGB. Within this data, the GB is able to insert specific data in packets to communicate back to PTGB.
A packet sent from PTGB is structured as follows:
1 preamble byte - 0xFD
1 command byte - any value except 0xFD or 0xFE.
2 argument bytes - any value, passed along to the command.
1 16-bit pointer - any value
7 filler bytes - any value
The command byte determines the exact way the incoming packet is interpretes.
- If the command byte value is between 0x00 and 0x05, the command is valid. If this is the case, argument bytes should be set to the appropriate value of the used command, the 16-bit pointer should point to the address where the incoming packet is stored.
- Else, the command byte is not valid, and this packet is interpreted as a request to read data starting from the address defined by the 16-bit pointer.
Argument bytes need to be present even if the command is invalid or if the command doesn't make use of them.
A packet sent from the GB is structured as follows:
1 preamble byte - 0xFD
2 LSB bytes - between 0x00 and 0x0F. The first byte encodes LSB info on the first 4 data bytes, the second encodes LSB info on the last 4 bytes.
8 data bytes - between 0x00 and 0x7F. Data bytes are shifted right once before being sent. The LSB is captured and stores within the LSB bytes.
1 checksum byte - between 0x00 and 0x7F. This checksum consists of the sum of various components:
- sum of all data bytes
- first LSB byte << 4 and second LSB byte
- high byte and low byte of read pointer (1 byte after the original address of the last data byte)
!! If the GB detects an error in the execution of a command, the first data byte's MSB will be set to 1 (resulting data value between 0x80 and 0xFF)
Exact behavior of command bytes
0x00 ReloadCurrentBox, no arguments used
Reloads the current box from SRAM. Use this as the first command byte before performing other commands.
0x01 TransferPokemon, 1st argument = secondary payload size, 2nd argument = box that should be transferred from
This command uses a secondary payload. The size of this secondary payload needs to be declared beforehand.
Prior to requesting a secondary payload, the program will use the second argument to load a specific box from SRAM. Box numbers are 0-indexed and range from 0x00 (box 1) to 0x0B(box 12). This load procedure currently cannot be skipped.
Once the secondary payload arrives, the program will verify its integrity and align the payload. Afterwards, it will use the information within this payload to remove pokémon from the current box. Once all transferred pokémon have been removed, the program will save the current box.
0x02 SoftReset, no arguments used
Instantly soft resets the game.
0x03 OpenSRAM, 1st argument = SRAM bank to be opened
Opens access to SRAM and switches to the SRAM bank determined by the 1st argument. SRAM will remain open afterwards.
0x04 CloseSRAM, no arguments used
Instantly closes access to SRAM.
0x05 RunSecondaryPayload, 1st argument = secondary payload size
This command uses a secondary payload. The size of this secondary payload needs to be declared beforehand.
Once the secondary payload arrives, the program will verify its integrity and align the payload. Afterwards, it will jump to the secondary payload and execute it.
IMPORTANT: if you want to report an error when executing this secondary payload, return with carry flag set.
IMPORTANT: if you want to report that the payload was executed safely, return with carry flag reset.
Secondary Payload structure
Both TransferPokemon and RunSecondaryPayload request a secondary payload.
This secondary payload can have a maximum size of 255 bytes and uses the following structure:
1 preamble byte - 0xFD
1 safety byte - 0xFF. Ensures that this secondary payload can never be misinterpreted as a valid command.
1 data size byte - any value. This value should be equal to the amount of received data bytes + 1
x data bytes - any value
1 checksum byte - any value. For the secondary payload to be valid, SUM(data bytes) + checksum must be equal to 0x00
!! If the checksum doesn't match, exit early and report an error instead of using the faulty payload.
IMPORTANT: the size argument should be equal to the size of the entire packet (including preamble, safety and data size bytes)
IMPORTANT: the data size byte should only be equal to the amount of data bytes + 1. This, data size byte + 3 = size argument.

View File

@@ -11,22 +11,22 @@ DEF PRINTWAITINGTEXT_SEARCH = 0x216B
; Serial_ExchangeBytes has different offsets based on language:
; RB
; EN: 0x216F
; FR: 0x216B
; DE: 0x216F
; IT: 0x216F
; SP: 0x216E
; EN: 0x216F 2125 - 2271
; FR: 0x216B 2121
; DE: 0x216F 2125
; IT: 0x216F 2125
; SP: 0x216E 2124
; Y
; EN: 00:1FCB
; FR: 00:1FC7
; DE: 00:1FD0
; IT: 00:1FCB
; SP: 00:1FCA
; EN: 00:1FCB 1F79
; FR: 00:1FC7 1f75
; DE: 00:1FD0 1F7e
; IT: 00:1FCB 1F79
; SP: 00:1FCA 1F78
; JP
; RG&RGA: 00:0BF1
; B : 00:214F
; Y0: 00:1FF4
; Y1-3: 00:1FFD
; RG&RGA: 00:0BF1 0BA7
; B : 00:214F 2105
; Y0: 00:1FF4 1fa2
; Y1-3: 00:1FFD 1fab
; start at 01:5883, look for 0xE5 0x3E

View File

@@ -0,0 +1,148 @@
INCLUDE "../../include/constants/charmap.asm"
INCLUDE "../../include/macros/const.asm"
INCLUDE "../../include/constants/serial_constants.asm"
INCLUDE "../../include/constants/pokemon_constants.asm"
INCLUDE "../../include/constants/symbols.asm"
INCLUDE "../../include/constants/hardware.inc"
INCLUDE "../../include/payload/payload.asm"
INCLUDE "../../include/payload/patches.asm"
INCLUDE "../../include/payload/settings.asm"
DEF hSerialConnectionStatus EQU 0xFF02
DEF LoadCurrentBoxDataBank EQU 0x1C
DEF LoadCurrentBoxData EQU 0x7690
DEF BankSwitch EQU 0x35D6
DEF CallFunctionInTable EQU 0x3D97
DEF PACKET_SIZE = 0xC
SECTION "Main", ROM0
Main:
LOAD "Payload", WRAM0[0xC508]
Payload:
ld b, LoadCurrentBoxDataBank
ld hl, LoadCurrentBoxData
call BankSwitch
.loopTransfer
ld hl, 0xC6DC ; perfect place to store incoming packets. TODO: on first pass, this will interpret the generic payload as part of an incoming packet. Check if this interferes with anything.
.skipPreamble
ld a, [hli]
cp SERIAL_PREAMBLE_BYTE
jr z, .skipPreamble ; standard sanity check
ld [0xC6DC], a ; copy the first non-preamble byte to the very start of the received packet. This is to prevent a 0xFD from occupying the first byte, which is important in case we run into a command failure.
push hl
ld hl, .commandTable
cp a, 4 ; PLACEHOLDER: LOOK INTO REPLACING WITH PROPER TABLE_SIZE DEFINITION
call c, CallFunctionInTable ; if command not in table, send data to PTGB instead. Afterwards, request another packet from PTGB.
pop hl ; hl should now point to the pointer that PTGB wants us to read data from.
ld a, [hli]
ld h, [hl]
ld l, a
ld de, 0xC6D2
push de
inc bc ; Serial_ExchangeBytes exits with bc = 0x0000, and CallFunctionInTable preserves bc. With this inc, bc = 0x0001
ld a, c
ldh [hSerialConnectionStatus], a ; is this needed?
.loop
ld a, [hli]
rra ; c flag is always 0 here, except if a failure state was detected in a command we ran this cycle. Failure state means 1st data byte > 0x80
push af ; store new c flag for later
inc de
ld [de], a
add a, b
ld b, a ; calculates checksum
pop af
ld a, c ; stores LSB bytes that were extracted using rra
adc a, a ; shifts a left, then adds the current carry flag to a. If shifted for a total of 8 times, a will overflow and we escape the loop.
ld c, a
jr nc, .loop
add a, c
add a, h
add a, l
res 7, a ; ensure that the checksum is never equal to 0xFE
ld [de], a ; load in the checksum
inc de ; de now points to 0xC6DC, where the next packet will arrive.
pop hl
ld a, c
and 0x0F ; extract the lower nybble.
ld [hld], a ; 4 LSB get stored here.
xor c ; I love xor magic, this stores the higher nybble in a.
swap a
ld [hld], a ; 4 MSB get stored here, hl now points to 0xC6D0, where the next packet will be sent from.
ld bc, PACKET_SIZE
call Serial_ExchangeBytes
jr .loopTransfer
.commandTable
dw TransferPokemon
dw Start ; R/B don't have CGB specific logic, so reset without caring for register states.
dw OpenSRAM
dw CloseSRAM
ReadAdditionalCommandByte: ; fetch the current command pointer within the stack, read the command byte at the current pointer, then increment the pointer. Only run this if we are executing a command and don't have additional stuff placed on the stack.
ld hl, sp + 0x0C ; this should reset the carry flag, hl points to the part of the stack that points to directly after the current command byte.
ld a, [hl]
inc [hl] ; since we are reading an additional command byte, increment the pointer.
inc hl
ld h, [hl]
ld l, a
ld a, [hl]
ret ; hl contains a pointer to the next command byte, while a contains the current command byte.
TransferPokemon:
ld hl, 0xC610
ld d, h
ld e, l
push de
push de
ld bc, 0x1F
call Serial_ExchangeBytes
pop hl
.skipPreamble
ld a, [hli]
cp a, SERIAL_PREAMBLE_BYTE
jr z, .skipPreamble
.checksumPacket
add a, c ; due to Serial_ExchangeBytes, bc = 0000 when we enter
ld c, a
ld a, [hli]
cp a, 0xFF
jr nz, .checksumPacket
ld a, c
sub [hl] ; if the checksum matches, set z flag.
scf ; in case the checksum, set the carry flag so we can communicate this failure state.
ret nz
inc a
ld [wRemoveMonFromBox], a ; a non-zer value in this address indicates we'll be removing stuff from the current active box.
.removalLoop
pop hl
ld a, [hli]
cp a, 0xFF
jr z, .saveBox
push hl
ld hl, wBoxCount
cp a, [hl]
ld [wWhichPokemon], a
call nc, RemovePokemon
jr .removalLoop
.saveBox
ld b, SaveCurrentBoxDataBank
ld hl, SaveCurrentBoxData ; resets carry flag
jp BankSwitch ; preserves flags
OpenSRAM: ; it is deeply funny that R/B do not have their own dedicated openSRAM or closeSRAM routines
call ReadAdditionalCommandByte ; inelegant solution to the constraints of CallFunctionInTable
ld h, 0xA
ld [hl], h
ld h, 0x40
ld [hl], a
ret
CloseSRAM:
ld h, 0
ld [hl], a
ret
ENDL

View File

@@ -0,0 +1,154 @@
INCLUDE "../../include/constants/charmap.asm"
INCLUDE "../../include/macros/const.asm"
INCLUDE "../../include/constants/serial_constants.asm"
INCLUDE "../../include/constants/pokemon_constants.asm"
INCLUDE "../../include/constants/symbols.asm"
INCLUDE "../../include/constants/hardware.inc"
INCLUDE "../../include/payload/payload.asm"
INCLUDE "../../include/payload/patches.asm"
INCLUDE "../../include/payload/settings.asm"
DEF hSerialConnectionStatus EQU 0xFF02
DEF LoadCurrentBoxDataBank EQU 0x1C
DEF LoadCurrentBoxData EQU 0x7690
DEF BankSwitch EQU 0x35D6
DEF CallFunctionInTable EQU 0x3D97
DEF PACKET_SIZE = 0xC
SECTION "Main", ROM0
Main:
LOAD "Payload", WRAM0[0xC508]
Payload:
.loopTransfer
ld hl, 0xC6DC ; perfect place to store incoming packets. TODO: on first pass, this will interpret the generic payload as part of an incoming packet. Check if this interferes with anything.
.skipPreamble
res 7, [hl] ; if current pointer == 0xFD, change so that rra with carry flag doesn't create 0xFE. If command byte, this functionally does nothing.
ld a, [hli]
cp SERIAL_PREAMBLE_BYTE - 0x80
jr z, .skipPreamble ; standard sanity check. If current byte does not match preamble, then we've loaded command byte 1 in register a.
ld b, [hl] ; load argument byte 1
inc hl
ld c, [hl] ; load argument byte 2
inc hl
push hl
ld hl, .commandTable
cp a, (.end - .commandTable) / 2 ; number of valid commands.
call c, CallFunctionInTable ; if command not in table, send data to PTGB instead. Afterwards, request another packet from PTGB.
pop hl ; hl should now point to the pointer that PTGB wants us to read data from.
ld a, [hli]
ld h, [hl]
ld l, a
ld de, 0xC6D2
push de
ld bc, 1
ld a, c
ldh [hSerialConnectionStatus], a ; is this needed?
.loop
ld a, [hli]
rra ; c flag is always 0 here, except if a failure state was detected in a command we ran this cycle. Failure state means 1st data byte > 0x80
push af ; store new c flag for later
inc de
ld [de], a
add a, b
ld b, a ; calculates checksum
pop af
ld a, c ; register c stores LSB bytes that were extracted using rra
adc a, a ; shifts a left, then adds the current carry flag to a. If shifted for a total of 8 times, a will overflow and we escape the loop.
ld c, a
jr nc, .loop
add a, c
add a, h
add a, l
res 7, a ; ensure that the checksum is never equal to 0xFE
ld [de], a ; load in the checksum
inc de ; de now points to 0xC6DC, where the next packet will arrive.
pop hl
ld a, c
and 0x0F ; extract the lower nybble.
ld [hld], a ; 4 LSB get stored here.
xor c ; I love xor magic, this stores the higher nybble in a.
swap a
ld [hld], a ; 4 MSB get stored here, hl now points to 0xC6D0, where the next packet will be sent from.
ld bc, PACKET_SIZE
call Serial_ExchangeBytes
jr .loopTransfer
.commandTable
dw ReloadCurrentBox
dw TransferPokemon
dw Start ; R/B don't have CGB specific logic, so reset without caring for register states.
dw OpenSRAM
dw CloseSRAM
dw RunSecondaryPayload
.end
ReloadCurrentBox:
ld b, LoadCurrentBoxDataBank
ld hl, LoadCurrentBoxData ; always resets carry flag at the end
jp BankSwitch ; preserves carry flag on return
TransferPokemon:
ld de, 0xC610
ld hl, 0xC5D0
push de
ld bc, 0x20 ; 0xFF + checksum + full box list + 0xFF + checksum
call Serial_ExchangeBytes
pop hl
.skipPreamble
ld a, [hli]
cp a, SERIAL_PREAMBLE_BYTE
jr z, .skipPreamble
ld a, [hli]
push hl
.checksumPacket
add a, c ; due to Serial_ExchangeBytes, bc = 0000 when we enter
ld c, a
ld a, [hli]
cp a, 0xFF ; once we've hit the terminator, bail and compare with the expected checksum
jr nz, .checksumPacket
and a, c ; c should be 0 at this point
scf ; in case the checksum does not match, set the carry flag so we can communicate this failure state.
ret nz
inc a
ld [wRemoveMonFromBox], a ; a non-zero value in this address indicates we'll be removing stuff from the current active box.
.removalLoop ; implementation of Gears' removal loop
pop hl
ld a, [hli]
cp a, 0xFF
jr z, .saveBox ; possible to optimize this jr out?
push hl
ld hl, wBoxCount
cp a, [hl]
ld [wWhichPokemon], a
call nc, RemovePokemon ; funny thing here, RemovePokemon is located in bank 00 and just contains a jpfar to _RemovePokemon
jr .removalLoop
.saveBox
ld b, SaveCurrentBoxDataBank
ld hl, SaveCurrentBoxData ; resets carry flag
jp BankSwitch ; preserves flags
OpenSRAM: ; opens SRAM bank to the bank described in command byte 2.
ld h, 0xA
ld [hl], h
ld h, 0x40
ld [hl], b
and a ; reset carry flag
ret
CloseSRAM:
xor a ; reset carry flag
ld h, a
ld [hl], a
ret
RunSecondaryPayload ; loads new payload of size bc, aligns it, verifies it, then executes it.
ld de, 0xC610
ld hl, 0xC5D0
push de
call Serial_ExchangeBytes ; bc is set by the argument bytes
pop hl
ENDL

Binary file not shown.

View File

@@ -0,0 +1,175 @@
INCLUDE "../../include/constants/charmap.asm"
INCLUDE "../../include/macros/const.asm"
INCLUDE "../../include/constants/serial_constants.asm"
INCLUDE "../../include/constants/pokemon_constants.asm"
INCLUDE "../../include/constants/symbols.asm"
INCLUDE "../../include/constants/hardware.inc"
INCLUDE "../../include/payload/payload.asm"
INCLUDE "../../include/payload/patches.asm"
INCLUDE "../../include/payload/settings.asm"
DEF hSerialConnectionStatus EQU 0xFF02
DEF LoadCurrentBoxDataBank EQU 0x1C
DEF LoadCurrentBoxData EQU 0x7690
DEF BankSwitch EQU 0x35D6
DEF CallFunctionInTable EQU 0x3D97
DEF PACKET_SIZE = 0xC
DEF StopAllSounds = 0x200E
DEF TextCommandProcessor = 0x1B40
DEF wCurrentMenuItem = 0xCC26
SECTION "Main", ROM0
Main:
LOAD "Payload", WRAM0[0xC508]
Payload:
db 0xFD
.loopTransfer
ld hl, 0xC6DC ; perfect place to store incoming packets. TODO: on first pass, this will interpret the generic payload as part of an incoming packet. Check if this interferes with anything.
.skipPreamble
res 7, [hl] ; if current pointer == 0xFD, change so that rra with carry flag doesn't create 0xFE. If command byte, this functionally does nothing.
ld a, [hli]
cp SERIAL_PREAMBLE_BYTE - 0x80
jr z, .skipPreamble ; standard sanity check. If current byte does not match preamble, then we've loaded command byte 1 in register a.
ld b, [hl] ; load argument byte 1
inc hl
ld c, [hl] ; load argument byte 2
inc hl
push hl
ld hl, .commandTable
cp a, (.end - .commandTable) / 2 ; number of valid commands.
call c, CallFunctionInTable ; if command not in table, send data to PTGB instead. Afterwards, request another packet from PTGB.
pop hl ; hl should now point to the pointer that PTGB wants us to read data from.
ld a, [hli]
ld h, [hl]
ld l, a
ld de, 0xC6D2
push de
ld bc, 1
ld a, c
ldh [hSerialConnectionStatus], a ; is this needed?
.loop
ld a, [hli]
rra ; c flag is always 0 here, except if a failure state was detected in a command we ran this cycle. Failure state means 1st data byte > 0x80
push af ; store new c flag for later
inc de
ld [de], a
add a, b
ld b, a ; calculates checksum
pop af
ld a, c ; register c stores LSB bytes that were extracted using rra
adc a, a ; shifts a left, then adds the current carry flag to a. If shifted for a total of 8 times, a will overflow and we escape the loop.
ld c, a
jr nc, .loop
add a, c
add a, h
add a, l
res 7, a ; ensure that the checksum is never equal to 0xFE
ld [de], a ; load in the checksum
inc de ; de now points to 0xC6DC, where the next packet will arrive.
pop hl
ld a, c
and 0x0F ; extract the lower nybble.
ld [hld], a ; 4 LSB get stored here.
xor c ; I love xor magic, this stores the higher nybble in a.
swap a
ld [hld], a ; 4 MSB get stored here, hl now points to 0xC6D0, where the next packet will be sent from.
ld bc, PACKET_SIZE
call Serial_ExchangeBytes
jr .loopTransfer
.commandTable
dw ReloadCurrentBox
dw TransferPokemon
dw Start ; R/B don't have CGB specific logic, so reset without caring for register states.
dw OpenSRAM
dw CloseSRAM
dw RunSecondaryPayload
.end
VerifySecondaryPayload: ; checks if payload matches expected size and passes verification.
ld c, b
ld b, 0
ld de, 0xC610
ld hl, 0xC5D0
push de
call Serial_ExchangeBytes
pop hl
push hl
.skipPreambleAndFF
ld a, [hli]
inc a
jr z, .skipPreambleAndFF
ld c, [hl] ; c now equals the size of the payload's data
inc hl
pop de
push de
.checksumLoop
ld a, [hli]
ld [de], a
inc de
add a, b
ld b, a
dec c
jr z, .checksumLoop
pop hl
and a
ret z
scf
pop hl
ret
ReloadCurrentBox:
ld hl, LoadCurrentBoxData ; always resets carry flag at the end
.bankSwitch
ld b, LoadCurrentBoxDataBank
jp BankSwitch ; preserves carry flag on return
TransferPokemon: ; possible usecase: use second argument byte to change box?
ld a, c
ld [wCurrentMenuItem], a
push bc
ld a, 0xb ; enable vblank interrupt so that a sound effect can play
ldh [rIE], a
ld hl, 0x78CD
call ReloadCurrentBox.bankSwitch
ld a, 0x8
ldh [rIE], a
pop bc
call VerifySecondaryPayload
inc a
ld [wRemoveMonFromBox], a ; a non-zero value in this address indicates we'll be removing stuff from the current active box.
.removalLoop ; implementation of Gears' removal loop
ld a, [hli]
cp a, 0xFF
jr z, .saveBox ; possible to optimize this jr out?
push hl
ld hl, wBoxCount
cp a, [hl]
ld [wWhichPokemon], a
call nc, RemovePokemon ; funny thing here, RemovePokemon is located in bank 00 and just contains a jpfar to _RemovePokemon
pop hl
jr .removalLoop
.saveBox
ld hl, SaveCurrentBoxData ; resets carry flag
jr ReloadCurrentBox.bankSwitch
OpenSRAM: ; opens SRAM bank to the bank described in argument byte 1.
ld h, 0xA
ld [hl], h
ld h, 0x40
ld [hl], b
and a ; reset carry flag
ret
CloseSRAM:
xor a ; reset carry flag
ld h, a
ld [hl], a
ret
RunSecondaryPayload: ; loads new payload of size bc, aligns it, verifies it, then executes it.
call VerifySecondaryPayload
jp hl
ENDL