Poke_Transporter_GB/source/gameboy_colour.cpp
2026-01-11 17:09:30 -05:00

662 lines
16 KiB
C++

// Loosely based on code created by StevenChaulk
// Source: https://github.com/stevenchaulk/arduino-poke-gen2
#include <tonc.h>
#include <stdarg.h>
#include <inttypes.h>
#include "libraries/nanoprintf/nanoprintf.h"
#include "libstd_replacements.h"
#include "gameboy_colour.h"
#include "pokemon_trade.h"
#include "script_array.h"
#include "debug_mode.h"
#include "interrupt.h"
#include "text_engine.h"
#include "global_frame_controller.h"
#include "background_engine.h"
#include "sprite_data.h"
#include "text_data_table.h"
#include "libraries/Pokemon-Gen3-to-Gen-X/include/save.h"
#include "flash_mem.h"
#define DATA_PER_PACKET 8
#define PACKET_DATA_START 2
#define PACKET_DATA_AT(i) (PACKET_DATA_START + (i * 2))
#define PACKET_FLAG_AT(i) (PACKET_DATA_START + (i * 2) + 1)
#define PACKET_CHECKSUM (PACKET_DATA_START + (2 * DATA_PER_PACKET))
#define PACKET_LOCATION_UPPER (PACKET_CHECKSUM + 1)
#define PACKET_LOCATION_LOWER (PACKET_CHECKSUM + 2)
// 0xFD, 0x00, data bytes per packet, flag bytes per packet, the checksum, and two location bytes
#define PACKET_SIZE (1 + 1 + (2 * DATA_PER_PACKET) + 1 + 2) // Originally 13
#define TIMEOUT 2
#define TIMEOUT_ONE_LENGTH 1000000 // Maybe keep a 10:1 ratio between ONE and TWO?
#define TIMEOUT_TWO_LENGTH 100000
#define hs 0
#define ack 1
#define menu 2
#define pretrade 3
#define trade 4
#define party_preamble 5
#define colosseum 6
#define cancel 7
#define trade_data 8
#define box_preamble 9
#define box_data 10
#define end1 11
#define reboot 12
#define remove_array_preamble 13
#define send_remove_array 14
#define end2 15
const int MODE = 1; // mode=0 will transfer pokemon data from pokemon.h
// mode=1 will copy pokemon party data being received
LinkSPI linkSPIInstance;
LinkSPI *linkSPI = &linkSPIInstance;
uint8_t in_data;
uint8_t out_data;
uint frame;
connection_state_t connection_state;
int counter;
int data_counter = 0;
int gen_1_room_counter = 0;
int gen;
int trade_pokemon;
int FF_count;
int zero_count;
int state;
int mosi_delay = 4; // inital delay, speeds up once sending PKMN
int received_offset = 0;
int next_offset = 0;
int packet_index = 0;
bool failed_packet;
bool init_packet;
bool test_packet_fail = false;
bool end_of_data;
byte data_packet[PACKET_SIZE];
#define SPI_TEXT_OUT_ARRAY_ELEMENT_SIZE 64
// Here's a compilation check to ensure that the size of these structs match our expectations.
// Just update it if you changed the struct members. The data-generator process prints their actual sizes.
static_assert(sizeof(struct GB_ROM) == 136);
static_assert(sizeof(struct ROM_DATA) == 160);
int link_cable_array_index = 0;
int link_cable_memory_section_index = 0;
void print(const char *format, ...)
{
va_list args;
va_start(args, format);
// 10 elements of 64 bytes, zero-initialized.
char spi_text_out_array[10][SPI_TEXT_OUT_ARRAY_ELEMENT_SIZE] = {
{0},
{0},
{0},
{0},
{0},
{0},
{0},
{0},
{0},
{0}};
for (int i = 10; i > 0; i--)
{
strncpy(spi_text_out_array[i], spi_text_out_array[i - 1], SPI_TEXT_OUT_ARRAY_ELEMENT_SIZE);
}
npf_vsnprintf(spi_text_out_array[0], SPI_TEXT_OUT_ARRAY_ELEMENT_SIZE, format, args);
va_end(args);
tte_erase_rect(LEFT, TOP, RIGHT, BOTTOM);
tte_set_pos(LEFT, 0);
for (int j = 0; j < 10; j++)
{
ptgb_write("#{cx:0xE000}");
ptgb_write(spi_text_out_array[j]);
}
}
void setup(const u16 *debug_charset)
{
interrupt_init();
interrupt_set_handler(INTR_SERIAL, LINK_SPI_ISR_SERIAL);
interrupt_enable(INTR_SERIAL);
linkSPI->activate(LinkSPI::Mode::MASTER_256KBPS);
linkSPI->setWaitModeActive(false);
state = hs;
in_data = 0;
out_data = 0;
frame = 0;
connection_state = NOT_CONNECTED;
counter = 0;
gen = 0;
trade_pokemon = -1;
FF_count = 0;
zero_count = 0;
next_offset = 0;
received_offset = 0;
packet_index = 0;
failed_packet = false;
init_packet = true;
end_of_data = false;
create_textbox(5, 1, 128, 60, true);
{
u8 general_text_table_buffer[2048];
text_data_table general_text(general_text_table_buffer);
general_text.decompress(get_compressed_text_table(GENERAL_INDEX));
ptgb_write(general_text.get_text_entry(GENERAL_connecting), true);
}
}
byte handleIncomingByte(byte in, byte *box_data_storage, byte *curr_payload, GB_ROM *curr_gb_rom, PokeBox *box, const u16 *debug_charset, bool cancel_connection)
{
// TODO: Change to a switch statement
if (state == hs)
{
mosi_delay = 4;
if (curr_gb_rom->generation == 2)
{
state = ack;
return 0x00;
}
if (in == 0x00)
{
state = ack;
return 0x01;
}
}
else if (state == ack)
{
if (curr_gb_rom->generation == 2)
{
if (in == 0x61)
{
state = menu;
return 0x61;
}
return 0x01;
}
else
{
if (in == 0x00)
{
state = menu;
return 0x00;
}
else if (in == 0x02)
{
state = hs;
return 0x02;
}
}
}
else if (state == menu)
{
if (in == 0x60 || in == 0x61)
{
tte_erase_rect(0, 0, H_MAX, V_MAX);
tte_set_pos(40, 24);
{
u8 general_text_table_buffer[2048];
text_data_table general_text(general_text_table_buffer);
general_text.decompress(get_compressed_text_table(GENERAL_INDEX));
ptgb_write(general_text.get_text_entry(curr_gb_rom->version != YELLOW_ID ? GENERAL_link_success : GENERAL_link_success_yellow), true);
}
link_animation_state(STATE_NO_ANIM);
state = pretrade;
data_counter = 0;
return in;
}
else if (in == 0x02)
{
state = hs;
return 0x02;
}
else
{
return in;
}
}
else if (state == pretrade)
{
if (data_counter == 16)
{
data_counter = 0;
state = trade;
}
data_counter++;
if (curr_gb_rom->generation == 2)
{
return 0x61;
}
else if (curr_gb_rom->generation == 1 && curr_gb_rom->version == YELLOW_ID)
{
return 0xD5;
}
else
{
return 0xD4;
}
}
else if (state == trade)
{
if (in == 0xfd)
{
tte_erase_rect(0, 0, H_MAX, V_MAX);
tte_set_pos(40, 24);
{
u8 general_text_table_buffer[2048];
text_data_table general_text(general_text_table_buffer);
general_text.decompress(get_compressed_text_table(GENERAL_INDEX));
ptgb_write(general_text.get_text_entry(GENERAL_transferring), true);
}
link_animation_state(STATE_TRANSFER);
mosi_delay = 1;
state = party_preamble;
}
return in;
}
else if (state == party_preamble)
{
if (in != 0xfd)
{
state = trade_data;
return exchange_parties(in, curr_payload);
}
return in;
}
else if (state == trade_data)
{
if (data_counter >= curr_gb_rom->payload_size)
{
if (in == 0xFD)
{
state = box_preamble;
init_packet = true;
}
else
{
return 0x00;
}
}
return exchange_parties(in, curr_payload);
}
else if (state == box_preamble)
{
if (in != 0xFD)
{
state = box_data;
return exchange_boxes(in, box_data_storage, curr_gb_rom, debug_charset);
}
return in;
}
else if (state == box_data)
{
return exchange_boxes(in, box_data_storage, curr_gb_rom, debug_charset);
}
else if (state == reboot)
{
data_counter = 0;
state = remove_array_preamble;
return 0xFD;
}
else if (state == remove_array_preamble)
{
if (in != 0xFD)
{
state = send_remove_array;
return exchange_remove_array(in, box, cancel_connection);
}
return in;
}
else if (state == send_remove_array)
{
if (data_counter >= 29) // This assumes the preamble does not count towards the number of bytes sent over the cable
{
state = end2;
}
data_counter++;
return exchange_remove_array(in, box, cancel_connection);
}
return in;
}
int loop(byte *box_data_storage, byte *curr_payload, GB_ROM *curr_gb_rom, PokeBox *box, const u16 *debug_charset, bool cancel_connection)
{
#define LINE_WIDTH 21
#define NUM_LINES 8
int counter = 0;
char stuff[NUM_LINES][LINE_WIDTH];
while (true)
{
if (PRINT_LINK_DATA && key_held(KEY_L))
{
while (!key_hit(KEY_R))
{
global_next_frame();
}
global_next_frame();
}
// TODO: Restore Errors
in_data = linkSPI->transfer(out_data);
if (PRINT_LINK_DATA && !key_held(KEY_DOWN))
{
// tte_set_margins(0, 0, H_MAX, V_MAX);
// print("%d: [%d][%d][%" PRIu8 "][%" PRIu8 "]\n\n", counter, data_counter, state, in_data, out_data);
for (int i = 0; i < NUM_LINES; i++)
{
// ptgb_write_debug(debug_charset, "\n", true);
for (int j = 0; j < LINE_WIDTH; j++)
{
stuff[i][j] = stuff[i + 1][j];
}
stuff[i][20] = '\n';
}
n2hexstr(&stuff[NUM_LINES - 1][0], counter & 0xFFFFFF, 6);
stuff[NUM_LINES - 1][6] = ':';
n2hexstr(&stuff[NUM_LINES - 1][7], data_counter & 0xFFFF, 4);
stuff[NUM_LINES - 1][11] = '|';
n2hexstr(&stuff[NUM_LINES - 1][12], state & 0xFF, 2);
stuff[NUM_LINES - 1][14] = '|';
n2hexstr(&stuff[NUM_LINES - 1][15], in_data & 0xFF, 2);
stuff[NUM_LINES - 1][17] = '|';
n2hexstr(&stuff[NUM_LINES - 1][18], out_data & 0xFF, 2);
stuff[NUM_LINES - 1][20] = '\0';
create_textbox(0, 0, 125, 80, false);
ptgb_write_debug(debug_charset, *stuff, true);
}
else if (WRITE_CABLE_DATA_TO_SAVE)
{
global_memory_buffer[link_cable_array_index] = in_data;
link_cable_array_index++;
global_memory_buffer[link_cable_array_index] = out_data;
link_cable_array_index++;
if (link_cable_array_index >= 0x1000)
{
copy_ram_to_save(&global_memory_buffer[0], 0x1000 * link_cable_memory_section_index, 0x1000);
link_cable_memory_section_index++;
link_cable_array_index = link_cable_array_index % 0x1000;
}
}
out_data = handleIncomingByte(in_data, box_data_storage, curr_payload, curr_gb_rom, box, debug_charset, cancel_connection);
if (FF_count > (15 * 60))
{
return COND_ERROR_DISCONNECT;
}
if (zero_count > (5 * 60))
{
// return COND_ERROR_COM_ENDED;
}
if (connection_state == COLOSSEUM)
{
return COND_ERROR_COLOSSEUM;
}
if (state == end1)
{
state = reboot;
if (WRITE_CABLE_DATA_TO_SAVE)
{
for (int i = 0; i < 16; i++)
{
global_memory_buffer[(link_cable_array_index + i) % 0x1000] = 0xAA;
}
copy_ram_to_save(&global_memory_buffer[0], 0x1000 * link_cable_memory_section_index, 0x1000);
}
return 0;
}
if (state == end2)
{
return 0;
}
if (key_held(KEY_SELECT)) // Forces a disconnect... not sure why it would need to be locked behind debug mode
{
return COND_ERROR_DISCONNECT;
}
if (state != box_data)
{
FF_count = (in_data == 0xFF ? FF_count + mosi_delay : 0);
zero_count = (in_data == 0x00 ? zero_count + mosi_delay : 0);
}
counter++;
for (int i = 0; i < mosi_delay; i++)
{
global_next_frame();
}
}
};
byte exchange_parties(byte curr_in, byte *curr_payload)
{
int ret = curr_payload[data_counter];
data_counter += 1;
return ret;
};
byte exchange_boxes(byte curr_in, byte *box_data_storage, GB_ROM *curr_gb_rom, const u16 *debug_charset)
{
data_packet[packet_index] = curr_in;
if (packet_index == PACKET_SIZE - 1)
{
byte checksum = 0;
for (int i = 0; i < DATA_PER_PACKET; i++)
{
if (data_packet[PACKET_FLAG_AT(i)] == 1)
{
data_packet[PACKET_DATA_AT(i)] = 0xFE;
}
checksum += data_packet[PACKET_DATA_AT(i)];
}
checksum &= 0b01111111; // Reset the top bit so it matches the recieved range
if (!init_packet)
{
received_offset = (data_packet[PACKET_LOCATION_LOWER] | (data_packet[PACKET_LOCATION_UPPER] << 8)) - ((curr_gb_rom->wBoxDataStart & 0xFFFF) + DATA_PER_PACKET);
}
if (checksum == data_packet[PACKET_CHECKSUM] && !init_packet && !(test_packet_fail && received_offset == 128)) // Verify if the data matches the checksum
{
for (int i = 0; i < DATA_PER_PACKET; i++)
{
if (received_offset + i <= curr_gb_rom->box_data_size && received_offset + i >= 0)
{
box_data_storage[received_offset + i] = data_packet[PACKET_DATA_AT(i)];
}
}
}
else if (!init_packet)
{
failed_packet = true;
if (test_packet_fail)
{
test_packet_fail = false;
}
}
if (end_of_data)
{
state = end1;
}
else
{
state = box_preamble;
}
if (received_offset > curr_gb_rom->box_data_size + DATA_PER_PACKET)
{
end_of_data = true;
}
if (!init_packet)
{
if (failed_packet)
{
next_offset -= DATA_PER_PACKET;
if (next_offset < 0)
{
next_offset = 0;
}
failed_packet = false;
}
else
{
next_offset += DATA_PER_PACKET;
}
}
if (((next_offset + curr_gb_rom->wBoxDataStart) & 0xFF) == 0xFE)
{
next_offset -= 1; // Set back the offset if the byte sent would be 0xFE, since that would break the system
}
#define ROW_LENGTH 22
#define COLUMN_LENGTH 3 + DATA_PER_PACKET
if (SHOW_DATA_PACKETS)
{
char outArr[COLUMN_LENGTH][ROW_LENGTH];
for (int col = 0; col < COLUMN_LENGTH; col++)
{
for (int row = 0; row < ROW_LENGTH; row++)
{
if (row == ROW_LENGTH - 1 && col == COLUMN_LENGTH - 1)
{
outArr[col][row] = '\0';
}
else if (row == ROW_LENGTH - 1)
{
outArr[col][row] = '\n';
}
else
{
outArr[col][row] = ' ';
}
}
}
int currRow = 0;
strcpy(&outArr[currRow][0], "Checksum: ");
outArr[currRow][10] = ' ';
n2hexstr(&outArr[currRow][11], checksum, 2);
strcpy(&outArr[currRow][13], " = ");
n2hexstr(&outArr[currRow][16], data_packet[PACKET_CHECKSUM], 2);
outArr[currRow][18] = ' ';
currRow += 1;
for (int i = 0; i < DATA_PER_PACKET; i++)
{
outArr[currRow][0] = 'P';
n2hexstr(&outArr[currRow][1], i, 1);
strcpy(&outArr[currRow][2], ": ");
n2hexstr(&outArr[currRow][4], data_packet[PACKET_DATA_AT(i)], 2);
strcpy(&outArr[currRow][6], " (");
n2hexstr(&outArr[currRow][8], data_packet[PACKET_FLAG_AT(i)], 2);
outArr[currRow][10] = ')';
currRow += 1;
}
strcpy(&outArr[currRow][0], "RO: ");
n2hexstr(&outArr[currRow][4], received_offset, 4);
strcpy(&outArr[currRow][8], " FP: ");
n2hexstr(&outArr[currRow][14], failed_packet, 2);
outArr[currRow][16] = ' ';
currRow += 1;
strcpy(&outArr[currRow][0], "NO: ");
n2hexstr(&outArr[currRow][4], next_offset, 4);
strcpy(&outArr[currRow][8], " IP: ");
n2hexstr(&outArr[currRow][14], init_packet, 2);
outArr[currRow][16] = ' ';
create_textbox(0, 0, 125, 110, false);
link_animation_state(0);
ptgb_write_debug(debug_charset, *outArr, true);
while (!key_held(KEY_A))
{
global_next_frame();
}
global_next_frame();
}
packet_index = 0;
init_packet = false;
}
packet_index += 1;
switch (packet_index)
{
case 3:
if (end_of_data)
{
return 0xFF;
}
return (curr_gb_rom->wBoxDataStart + next_offset) >> 8;
case 2:
return (curr_gb_rom->wBoxDataStart + next_offset) >> 0;
default:
return 0;
}
};
byte exchange_remove_array(byte curr_in, PokeBox *box, bool cancel_connection)
{
for (int i = 29; i >= 0; i--)
{
if (box->getGen3Pokemon(i)->isValid && !cancel_connection)
{
box->removePokemon(i);
if (!(DONT_TRANSFER_POKEMON_AT_INDEX_X && i == POKEMON_INDEX_TO_SKIP))
{
return i;
}
}
}
return 0xFF;
}