diff --git a/include/link_handler.h b/include/link_handler.h index b02d6c5..6faae7e 100644 --- a/include/link_handler.h +++ b/include/link_handler.h @@ -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, }; diff --git a/source/link_handler.cpp b/source/link_handler.cpp index 9cedae4..92e7468 100644 --- a/source/link_handler.cpp +++ b/source/link_handler.cpp @@ -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: diff --git a/source/pokemon_party.cpp b/source/pokemon_party.cpp index 2c15c3e..c4fbd27 100644 --- a/source/pokemon_party.cpp +++ b/source/pokemon_party.cpp @@ -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); diff --git a/tools/gb-payload-generator/documentation/SpecificPayloadDocumentation.txt b/tools/gb-payload-generator/documentation/SpecificPayloadDocumentation.txt new file mode 100644 index 0000000..1befc15 --- /dev/null +++ b/tools/gb-payload-generator/documentation/SpecificPayloadDocumentation.txt @@ -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. + + + \ No newline at end of file diff --git a/tools/gb-payload-generator/include/payload/patches.asm b/tools/gb-payload-generator/include/payload/patches.asm index ca7babe..7609faf 100644 --- a/tools/gb-payload-generator/include/payload/patches.asm +++ b/tools/gb-payload-generator/include/payload/patches.asm @@ -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 diff --git a/tools/gb-payload-generator/src/payloads/TEMP WIP/specificPayloadGen1.asm b/tools/gb-payload-generator/src/payloads/TEMP WIP/specificPayloadGen1.asm new file mode 100644 index 0000000..471d2cb --- /dev/null +++ b/tools/gb-payload-generator/src/payloads/TEMP WIP/specificPayloadGen1.asm @@ -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 diff --git a/tools/gb-payload-generator/src/payloads/TEMP WIP/specificPayloadGen1v2.asm b/tools/gb-payload-generator/src/payloads/TEMP WIP/specificPayloadGen1v2.asm new file mode 100644 index 0000000..02a0bef --- /dev/null +++ b/tools/gb-payload-generator/src/payloads/TEMP WIP/specificPayloadGen1v2.asm @@ -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 diff --git a/tools/gb-payload-generator/src/payloads/outfile.bin b/tools/gb-payload-generator/src/payloads/outfile.bin new file mode 100644 index 0000000..f5356a7 Binary files /dev/null and b/tools/gb-payload-generator/src/payloads/outfile.bin differ diff --git a/tools/gb-payload-generator/src/payloads/specificPayloadGen1_RB_EN.asm b/tools/gb-payload-generator/src/payloads/specificPayloadGen1_RB_EN.asm new file mode 100644 index 0000000..76689ca --- /dev/null +++ b/tools/gb-payload-generator/src/payloads/specificPayloadGen1_RB_EN.asm @@ -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