Back to TONC

This commit is contained in:
Starport75 2023-06-09 18:31:19 -05:00
parent 973b24f538
commit 19f346ba42
9 changed files with 625 additions and 8 deletions

View File

@ -11,6 +11,8 @@ include $(DEVKITARM)/gba_rules
#---------------------------------------------------------------------------------
# the LIBGBA path is defined in gba_rules, but we have to define LIBTONC ourselves
#---------------------------------------------------------------------------------
LIBTONC := $(DEVKITPRO)/libtonc
LIBUGBA := $(DEVKITPRO)/libugba
#---------------------------------------------------------------------------------
# TARGET is the name of the output
@ -51,7 +53,7 @@ LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS := -lmm -libgba
LIBS := -ltonc -lugba
#---------------------------------------------------------------------------------
@ -59,7 +61,7 @@ LIBS := -lmm -libgba
# include and lib.
# the LIBGBA path should remain in this list if you want to use maxmod
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBGBA)
LIBDIRS := $(LIBGBA) $(LIBTONC) $(LIBUGBA)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional

237
include/LinkSPI.h Normal file
View File

@ -0,0 +1,237 @@
#ifndef LINK_SPI_H
#define LINK_SPI_H
// --------------------------------------------------------------------------
// An SPI handler for the Link Port (Normal Mode, 32bits).
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkSPI* linkSPI = new LinkSPI();
// - 2) (Optional) Add the interrupt service routines:
// irq_init(NULL);
// irq_add(II_SERIAL, LINK_SPI_ISR_SERIAL);
// // (this is only required for `transferAsync`)
// - 3) Initialize the library with:
// linkSPI->activate(LinkSPI::Mode::MASTER_256KBPS);
// // (use LinkSPI::Mode::SLAVE on the other end)
// - 4) Exchange 32-bit data with the other end:
// u8 data = linkSPI->transfer(0x1234);
// // (this blocks the console indefinitely)
// - 5) Exchange data with a cancellation callback:
// u8 data = linkSPI->transfer(0x1234, []() {
// u16 keys = ~REG_KEYS & KEY_ANY;
// return keys & KEY_START;
// });
// - 6) Exchange data asynchronously:
// linkSPI->transferAsync(0x1234);
// // ...
// if (linkSPI->getAsyncState() == LinkSPI::AsyncState::READY) {
// u8 data = linkSPI->getAsyncData();
// // ...
// }
// --------------------------------------------------------------------------
// considerations:
// - when using Normal Mode between two GBAs, use a GBC Link Cable!
// - only use the 2Mbps mode with custom hardware (very short wires)!
// - don't send 0xFFFFFFFF, it's reserved for errors!
// --------------------------------------------------------------------------
#include <tonc_core.h>
#define LINK_SPI_NO_DATA 0xFF
#define LINK_SPI_SIOCNT_NORMAL 0
#define LINK_SPI_BIT_CLOCK 0
#define LINK_SPI_BIT_CLOCK_SPEED 1
#define LINK_SPI_BIT_SI 2
#define LINK_SPI_BIT_SO 3
#define LINK_SPI_BIT_START 7
#define LINK_SPI_BIT_LENGTH 12
#define LINK_SPI_BIT_IRQ 14
#define LINK_SPI_BIT_GENERAL_PURPOSE_LOW 14
#define LINK_SPI_BIT_GENERAL_PURPOSE_HIGH 15
#define LINK_SPI_SET_HIGH(REG, BIT) REG |= 1 << BIT
#define LINK_SPI_SET_LOW(REG, BIT) REG &= ~(1 << BIT)
static volatile char LINK_SPI_VERSION[] = "LinkSPI/v5.0.2";
class LinkSPI {
public:
enum Mode { SLAVE, MASTER_256KBPS, MASTER_2MBPS };
enum AsyncState { IDLE, WAITING, READY };
bool isActive() { return isEnabled; }
void activate(Mode mode) {
this->mode = mode;
this->waitMode = false;
this->asyncState = IDLE;
this->asyncData = 0;
setNormalMode();
set32BitPackets();
setInterruptsOff();
disableTransfer();
if (mode == SLAVE)
setSlaveMode();
else {
setMasterMode();
if (mode == MASTER_256KBPS)
set256KbpsSpeed();
else if (mode == MASTER_2MBPS)
set2MbpsSpeed();
}
isEnabled = true;
}
void deactivate() {
isEnabled = false;
stopTransfer();
disableTransfer();
setGeneralPurposeMode();
mode = SLAVE;
waitMode = false;
asyncState = IDLE;
asyncData = 0;
}
u8 transfer(u8 data) {
return transfer(data, []() { return false; });
}
template <typename F>
u8 transfer(u8 data,
F cancel,
bool _async = false,
bool _customAck = false) {
if (asyncState != IDLE)
return LINK_SPI_NO_DATA;
setData(data);
if (_async) {
asyncState = WAITING;
setInterruptsOn();
} else {
setInterruptsOff();
}
enableTransfer();
while (isMaster() && waitMode && !isSlaveReady())
if (cancel()) {
disableTransfer();
setInterruptsOff();
asyncState = IDLE;
return LINK_SPI_NO_DATA;
}
startTransfer();
if (_async)
return LINK_SPI_NO_DATA;
while (!isReady())
if (cancel()) {
stopTransfer();
disableTransfer();
return LINK_SPI_NO_DATA;
}
if (!_customAck)
disableTransfer();
return getData();
}
void transferAsync(u8 data) {
transfer(
data, []() { return false; }, true);
}
template <typename F>
void transferAsync(u8 data, F cancel) {
transfer(data, cancel, true);
}
u8 getAsyncData() {
if (asyncState != READY)
return LINK_SPI_NO_DATA;
u8 data = asyncData;
asyncState = IDLE;
return data;
}
Mode getMode() { return mode; }
void setWaitModeActive(bool isActive) { waitMode = isActive; }
bool isWaitModeActive() { return waitMode; }
AsyncState getAsyncState() { return asyncState; }
void _onSerial(bool _customAck = false) {
if (!isEnabled || asyncState != WAITING)
return;
if (!_customAck)
disableTransfer();
setInterruptsOff();
asyncState = READY;
asyncData = getData();
}
void _setSOHigh() { setBitHigh(LINK_SPI_BIT_SO); }
void _setSOLow() { setBitLow(LINK_SPI_BIT_SO); }
bool _isSIHigh() { return isBitHigh(LINK_SPI_BIT_SI); }
private:
Mode mode = Mode::SLAVE;
bool waitMode = false;
AsyncState asyncState = IDLE;
u8 asyncData = 0;
bool isEnabled = false;
void setNormalMode() {
LINK_SPI_SET_LOW(REG_RCNT, LINK_SPI_BIT_GENERAL_PURPOSE_HIGH);
REG_SIOCNT = LINK_SPI_SIOCNT_NORMAL;
}
void setGeneralPurposeMode() {
LINK_SPI_SET_LOW(REG_RCNT, LINK_SPI_BIT_GENERAL_PURPOSE_LOW);
LINK_SPI_SET_HIGH(REG_RCNT, LINK_SPI_BIT_GENERAL_PURPOSE_HIGH);
}
void setData(u8 data) { REG_SIODATA8 = data; }
u8 getData() { return REG_SIODATA8; }
void enableTransfer() { _setSOLow(); }
void disableTransfer() { _setSOHigh(); }
void startTransfer() { setBitHigh(LINK_SPI_BIT_START); }
void stopTransfer() { setBitLow(LINK_SPI_BIT_START); }
bool isReady() { return !isBitHigh(LINK_SPI_BIT_START); }
bool isSlaveReady() { return !_isSIHigh(); }
void set32BitPackets() { setBitLow(LINK_SPI_BIT_LENGTH); }
void setMasterMode() { setBitHigh(LINK_SPI_BIT_CLOCK); }
void setSlaveMode() { setBitLow(LINK_SPI_BIT_CLOCK); }
void set256KbpsSpeed() { setBitLow(LINK_SPI_BIT_CLOCK_SPEED); }
void set2MbpsSpeed() { setBitHigh(LINK_SPI_BIT_CLOCK_SPEED); }
void setInterruptsOn() { setBitHigh(LINK_SPI_BIT_IRQ); }
void setInterruptsOff() { setBitLow(LINK_SPI_BIT_IRQ); }
bool isMaster() { return mode != SLAVE; }
bool isBitHigh(u8 bit) { return (REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { LINK_SPI_SET_HIGH(REG_SIOCNT, bit); }
void setBitLow(u8 bit) { LINK_SPI_SET_LOW(REG_SIOCNT, bit); }
};
extern LinkSPI* linkSPI;
inline void LINK_SPI_ISR_SERIAL() {
linkSPI->_onSerial();
}
#endif // LINK_SPI_H

81
include/gb_link.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef GB_LINK_H
/*
typedef enum {
FIRST_COMMUNICATION,
POST_SAVE,
SELECT_ROOM,
TABLE_INTERACT,
TEN_ZEROS_ONE,
SEVENTY_SIX,
TEN_ZEROS_TWO,
FINAL_DESTINATION,
SEND_POKEMON_DATA
} connection_state;
*/
typedef enum
{
NOT_CONNECTED,
CONNECTED,
TRADE_CENTRE,
COLOSSEUM
} connection_state_t;
typedef enum
{
INIT,
READY_TO_GO,
SEEN_FIRST_WAIT,
SENDING_RANDOM_DATA,
WAITING_TO_SEND_DATA,
SENDING_DATA,
SENDING_PATCH_DATA,
MIMIC,
TRADE_PENDING,
TRADE_CONFIRMATION,
DONE
} trade_centre_state_gen_II_t;
#define PKMN_BLANK 0x00
#define ITEM_1_HIGHLIGHTED 0xD0
#define ITEM_2_HIGHLIGHTED 0xD1
#define ITEM_3_HIGHLIGHTED 0xD2
#define ITEM_1_SELECTED 0xD4
#define ITEM_2_SELECTED 0xD5
#define ITEM_2_SELECTED 0xD5
#define ITEM_3_SELECTED 0xD6
#define GEN_II_CABLE_TRADE_CENTER 0xD1
#define GEN_II_CABLE_CLUB_COLOSSEUM 0xD2
#define PKMN_MASTER 0x01
#define PKMN_SLAVE 0x02
#define PKMN_MASTER_GEN_III 0x8FFF //??
#define PKMN_SLAVE_GEN_III 0xB9A0 //??
#define PKMN_CONNECTED_I 0x60
#define PKMN_CONNECTED_II 0x61
#define PKMN_WAIT 0x7F
#define PKMN_ACTION 0x60
#define PKMN_TRADE_CENTRE ITEM_1_SELECTED
#define PKMN_COLOSSEUM ITEM_2_SELECTED
#define PKMN_BREAK_LINK ITEM_3_SELECTED
#define TRADE_CENTRE_WAIT 0xFD
#define PLAYER_LENGTH_GEN_II 444 //11+8+2+(48*6)+(11*6)+(11*6)+3
#define GB_LINK_H
#include <tonc.h>
void log(std::string text);
void wait(u32 verticalLines);
inline void VBLANK() {}
void init();
int start_link();
#endif

30
include/interrupt.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef INTERRUPT_H
#define INTERRUPT_H
typedef enum {
INTR_VBLANK = 0,
INTR_HBLANK = 1,
INTR_VCOUNT = 2,
INTR_TIMER0 = 3,
INTR_TIMER1 = 4,
INTR_TIMER2 = 5,
INTR_TIMER3 = 6,
INTR_SERIAL = 7,
INTR_DMA0 = 8,
INTR_DMA1 = 9,
INTR_DMA2 = 10,
INTR_DMA3 = 11,
INTR_KEYPAD = 12,
INTR_GAMEPAK = 13,
INTR_NUMBER
} interrupt_index;
typedef void (*interrupt_vector)(void);
void interrupt_init(void);
void interrupt_set_handler(interrupt_index index, interrupt_vector function);
void interrupt_enable(interrupt_index index);
void interrupt_disable(interrupt_index index);
void interrupt_set_reference_vcount(unsigned long y);
#endif // INTERRUPT_H

View File

@ -1,6 +1,6 @@
#ifndef MIRROR_H
#define MIRROR_H
#include <gba.h>
#include <tonc.h>
#define MEM_CRAM 0x0E000000 //!< Cart RAM

View File

@ -1,4 +1,4 @@
#include <gba.h>
#include <tonc.h>
#include <string>
#include "mirror.h"
#include "gba_flash.h"

264
source/gb_link.cpp Normal file
View File

@ -0,0 +1,264 @@
#include <string>
#include <tonc.h>
#include "gb_link.h"
#include "LinkSPI.h"
#include "debug.h"
// (1) Create a LinkSPI instance
LinkSPI *linkSPI = new LinkSPI();
vu32 trainerData[111] = {0x8A918892, 0x50505050, 0x50505006, 0x9B9B9B9B, 0x9B9BFF25, 0xDA9B0021, 0x2B000025, 0xDA000087, 0x00000000, 0x00000000, 0x00007221, 0x231E0000, 0x46008581, 0x05000000, 0x13001300, 0x0A000900, 0x0B000B00, 0x0A9B0021, 0x2B000025, 0xDA000087, 0x00000000, 0x00000000, 0x00007221, 0x231E0000, 0x46008581, 0x05000000, 0x13001300, 0x0A000900, 0x0B000B00, 0x0A9B0021, 0x2B000025, 0xDA000087, 0x00000000, 0x00000000, 0x00007221, 0x231E0000, 0x46008581, 0x05000000, 0x13001300, 0x0A000900, 0x0B000B00, 0x0A9B0021, 0x2B000025, 0xDA000087, 0x00000000, 0x00000000, 0x00007221, 0x231E0000, 0x46008581, 0x05000000, 0x13001300, 0x0A000900, 0x0B000B00, 0x0A9B0021, 0x2B000025, 0xDA000087, 0x00000000, 0x00000000, 0x00007221, 0x231E0000, 0x46008581, 0x05000000, 0x13001300, 0x0A000900, 0x0B000B00, 0x0A9B0021, 0x2B000025, 0xDA000087, 0x00000000, 0x00000000, 0x00007221, 0x231E0000, 0x46008581, 0x05000000, 0x13001300, 0x0A000900, 0x0B000B00, 0x0A8A9188, 0x92505050, 0x50505050, 0x8A918892, 0x50505050, 0x5050508A, 0x91889250, 0x50505050, 0x50508A91, 0x88925050, 0x50505050, 0x508A9188, 0x92505050, 0x50505050, 0x8A918892, 0x50505050, 0x50505082, 0x988D8380, 0x9094888B, 0x50508298, 0x8D838090, 0x94888B50, 0x5082988D, 0x83809094, 0x888B5050, 0x82988D83, 0x80909488, 0x8B505082, 0x988D8380, 0x9094888B, 0x50508298, 0x8D838090, 0x94888B50, 0x50000000};
void init()
{
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
// (2) Add the interrupt service routines
}
std::string convertToString(char *a, int size)
{
int i;
std::string s = "";
for (i = 0; i < size; i++)
{
s = s + a[i];
}
return s;
}
int start_link()
{
init();
u8 SPIout = 0x00; // What the GBA is sending out to the GBC
u8 SPIin = 0x00; // What the GBA is receiving from the GBC
std::string outputArr[16] = {};
u32 mode = 0; // Trick game into entering trading room
bool firstTransfer = false;
u32 index = 0;
std::string bigOut = "";
// connection_state linkCable = POST_SAVE;
connection_state_t connection_state = NOT_CONNECTED;
trade_centre_state_gen_II_t trade_centre_state_gen_II = INIT;
int counter = 0;
byte in;
byte send = 0x16;
const int MODE = 1; // mode=0 will transfer pokemon data from pokemon.h
int trade_pokemon = -1;
while (true)
{
std::string output = "";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkSPI->isActive())
{
firstTransfer = true;
linkSPI->activate(LinkSPI::Mode::SLAVE);
}
else
{
// Title
auto modeName =
linkSPI->getMode() == LinkSPI::Mode::SLAVE ? "[slave]" : "[master]";
output += std::string(modeName) + "\n";
if (firstTransfer)
{
log(output + "Waiting for link cable...\nPress L and R to cancel");
firstTransfer = false;
}
// (4)/(5) Exchange 32-bit data with the other end
SPIin = linkSPI->transfer(SPIout, []()
{
bool exit = false;
u16 keys = ~REG_KEYS & KEY_ANY;
if ((keys & KEY_L) && (keys & KEY_R)){
exit = true;
}
return exit; });
in = SPIin;
byte send = 0x00;
switch(connection_state) {
case NOT_CONNECTED:
if(in == PKMN_MASTER)
send = PKMN_SLAVE;
else if(in == PKMN_BLANK)
send = PKMN_BLANK;
else if(in == PKMN_CONNECTED_II) {
send = PKMN_CONNECTED_II;
connection_state = CONNECTED;
}
break;
case CONNECTED:
if(in == PKMN_CONNECTED_II) //acknowledge connection
send = PKMN_CONNECTED_II;
else if(in == GEN_II_CABLE_TRADE_CENTER){ //acknowledge trade center selection
connection_state = TRADE_CENTRE;
send = GEN_II_CABLE_TRADE_CENTER;
}
else if(in == GEN_II_CABLE_CLUB_COLOSSEUM){ //acknowledge colosseum selection
connection_state = COLOSSEUM;
send = GEN_II_CABLE_CLUB_COLOSSEUM;
}
else {
send = in;
}
break;
case TRADE_CENTRE:
if(trade_centre_state_gen_II == INIT && in == 0x00) {
trade_centre_state_gen_II = READY_TO_GO;
send = 0x00;
} else if(trade_centre_state_gen_II == READY_TO_GO && in == 0xFD) {
trade_centre_state_gen_II = SEEN_FIRST_WAIT;
send = 0xFD;
} else if(trade_centre_state_gen_II == SEEN_FIRST_WAIT && in != 0x70) {
// random data of slave is ignored.
send = in;
trade_centre_state_gen_II = SENDING_RANDOM_DATA;
} else if(trade_centre_state_gen_II == SENDING_RANDOM_DATA && in == 0xFD) {
trade_centre_state_gen_II = WAITING_TO_SEND_DATA;
send = 0xFD;
} else if(trade_centre_state_gen_II == WAITING_TO_SEND_DATA && in != 0xFD) {
counter = 0;
// send first byte
switch(MODE){
case 0:
//send = pgm_read_byte(&(DATA_BLOCK_GEN_II[counter]));
//INPUT_BLOCK_GEN_II[counter] = in;
break;
case 1:
send = in;
break;
default:
send = in;
break;
}
counter++;
trade_centre_state_gen_II = SENDING_DATA;
} else if(trade_centre_state_gen_II == SENDING_DATA) {
switch(MODE){
case 0:
//send = pgm_read_byte(&(DATA_BLOCK_GEN_II[counter]));
//INPUT_BLOCK_GEN_II[counter] = in;
break;
case 1:
send = in;
break;
default:
send = in;
break;
}
counter++;
if(counter == PLAYER_LENGTH_GEN_II) {
trade_centre_state_gen_II = SENDING_PATCH_DATA;
}
} else if(trade_centre_state_gen_II == SENDING_PATCH_DATA && in == 0xFD) {
counter = 0;
send = 0xFD;
} else if(trade_centre_state_gen_II == SENDING_PATCH_DATA && in != 0xFD) {
send = in;
trade_centre_state_gen_II = MIMIC;
} else if(trade_centre_state_gen_II == MIMIC){
send = in;
} else if(trade_centre_state_gen_II == TRADE_PENDING && (in & 0x60) == 0x60) {
if (in == 0x6f) {
trade_centre_state_gen_II = READY_TO_GO;
send = 0x6f;
} else {
send = 0x60; // first pokemon
trade_pokemon = in - 0x60;
}
} else if(trade_centre_state_gen_II == TRADE_PENDING && in == 0x00) {
send = 0;
trade_centre_state_gen_II = TRADE_CONFIRMATION;
} else if(trade_centre_state_gen_II == TRADE_CONFIRMATION && (in & 0x60) == 0x60) {
send = in;
if (in == 0x61) {
trade_pokemon = -1;
trade_centre_state_gen_II = TRADE_PENDING;
} else {
trade_centre_state_gen_II = DONE;
}
} else if(trade_centre_state_gen_II == DONE && in == 0x00) {
send = 0;
trade_centre_state_gen_II = INIT;
} else {
send = in;
}
break;
default:
send = in;
break;
}
SPIout = send;
for (int i = 7; i > 0; i--)
{
outputArr[i] = outputArr[i - 1];
}
outputArr[0] = "TS: " + std::to_string((int)trade_centre_state_gen_II) + " CS: " + std::to_string((int)connection_state) +
" OUT: " + u8ToHexStr(SPIout) + " IN: " + u8ToHexStr(SPIin);
// Cancel
if ((keys & KEY_L) && (keys & KEY_R))
{
linkSPI->deactivate();
return 0;
}
}
// Print
VBlankIntrWait();
output = "";
for (int i = 0; i < 8; i++)
{
output += (outputArr[i] + "\n");
}
log(output);
}
return 0;
}
void log(std::string text)
{
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}
void wait(u32 verticalLines)
{
u32 count = 0;
u32 vCount = REG_VCOUNT;
while (count < verticalLines)
{
if (REG_VCOUNT != vCount)
{
count++;
vCount = REG_VCOUNT;
}
};
}

View File

@ -1,9 +1,12 @@
#include <gba.h>
#include <tonc.h>
#include <string>
#include "debug.h"
#include "mirror.h"
#include "gba_flash.h"
#include "interrupt.h"
#include "LinkSPI.h"
#include "gb_link.h"
// This file is autogenerated from the file in the graphics folder
#include "metr.h"
@ -112,7 +115,7 @@ int main(void)
}
if (key_released(KEY_START)){
start_link();
}

View File

@ -1,4 +1,4 @@
#include <gba.h>
#include <tonc.h>
#include "mirror.h"
#include "gba_flash.h"
@ -39,7 +39,7 @@ void initalize_memory_locations(){
}
// Reverses the endian of the given array
void reverse_endian(u8 *data, u8 size){
void reverse_endian(u8 *data, size_t size){
u8 temp;
for (int i = 0; i < (size/2); i++){
temp = data[i];