Skip to content

Commit

Permalink
Merge pull request #15 from afska/v6.0.3
Browse files Browse the repository at this point in the history
📻🌎 Native wireless adapter support for maxPlayers & Bugfixing
  • Loading branch information
afska authored Jan 6, 2024
2 parents e592884 + 8df5c69 commit cadda9e
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 71 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ Name | Return type | Description
`isActive()` | **bool** | Returns whether the library is active or not.
`activate()` | **bool** | Activates the library. When an adapter is connected, it changes the state to `AUTHENTICATED`. It can also be used to disconnect or reset the adapter.
`deactivate()` | **bool** | Puts the adapter into a low consumption mode and then deactivates the library. It returns a boolean indicating whether the transition to low consumption mode was successful.
`serve([gameName], [userName], [gameId], [isUpdate])` | **bool** | Starts broadcasting a server and changes the state to `SERVING`. You can, optionally, provide a `gameName` (max `14` characters), a `userName` (max `8` characters), and a `gameId` *(0 ~ 0x7FFF)* that games will be able to read. If `isUpdate` is true, this method only updates the broadcast data.
`serve([gameName], [userName], [gameId])` | **bool** | Starts broadcasting a server and changes the state to `SERVING`. You can, optionally, provide a `gameName` (max `14` characters), a `userName` (max `8` characters), and a `gameId` *(0 ~ 0x7FFF)* that games will be able to read. If the adapter is already serving, this method only updates the broadcast data.
`getServers(servers, [onWait])` | **bool** | Fills the `servers` array with all the currently broadcasting servers. This action takes 1 second to complete, but you can optionally provide an `onWait()` function which will be invoked each time VBlank starts.
`getServersAsyncStart()` | **bool** | Starts looking for broadcasting servers and changes the state to `SEARCHING`. After this, call `getServersAsyncEnd(...)` 1 second later.
`getServersAsyncEnd(servers)` | **bool** | Fills the `servers` array with all the currently broadcasting servers. Changes the state to `AUTHENTICATED` again.
Expand Down
26 changes: 21 additions & 5 deletions docs/wireless_adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ Whenever either side expects something to be sent from the other (as SPI is alwa

Both Pokemon games and the multiboot ROM that the adapter sends when no cartridge is inserted use `0x003C0420`.

🔝 For a game, the most important bits are bits `16-17` (let's call this `maxPlayers`), which specify the maximum number of allowed players:
- `00`: 5 players (1 host and 4 clients)
- `01`: 4 players
- `10`: 3 players
- `11`: 2 players

⚠️ Clients must always set `maxPlayers` to `00`.

#### Broadcast - `0x16`

[![Image without alt text or caption](img/0x16.png)](img/0x16.png)
Expand Down Expand Up @@ -265,14 +273,22 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
- Clients cannot connect, even if they already know the host ID (`FinishConnection` will fail).
- Calls to `AcceptConnections` on the host side will fail.

#### BroadcastRead - `0x1d`, `0x1e` and `0x1c`
#### BroadcastRead - `0x1c`, `0x1d` and `0x1e`

[![Image without alt text or caption](img/0x1d.png)](img/0x1d.png)

* Send length: 0, response length: 7 \* number of broadcasts
* Send length: 0, response length: 7 \* number of broadcasts (maximum: 4)

* All currently broadcasting devices are returned here along with an ID at the start of each.
* IDs have 16 bits.
* All currently broadcasting devices are returned here along with a word of **metadata** (the metadata word first, then 6 words with broadcast data).
* The metadata contains:
* First 2 bytes: Server ID. IDs have 16 bits.
* 3rd byte: Available slots. This can be used to check whether a player can join a room or not.
* `0b00`: No one is connected yet (just the host).
* `0b01`: There is 1 connected client, and there's room for more.
* `0b10`: There are 2 connected clients, and there's room for more.
* `0b11`: There are 3 connected clients, and there's room for more.
* `0xff`: The server is full. The number of connected players is unknown, as it depends on `maxPlayers` (see [Setup](#setup---0x17)).
* 4th byte: Zero.

🆔 IDs are randomly generated. Each time you broadcast or connect, the adapter assigns you a new id.

Expand All @@ -297,7 +313,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg

* Send length: 1, response length: 0

* Send the ID of the adapter you want to connect to from [BroadcastRead](#broadcastread---0x1d-0x1e-and-0x1c).
* Send the ID of the adapter you want to connect to from [BroadcastRead](#broadcastread---0x1c-0x1d-and-0x1e).

#### IsFinishedConnect - `0x20`

Expand Down
22 changes: 19 additions & 3 deletions examples/LinkUniversal_basic/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ void init() {
int main() {
init();

log("Press A to start\n\n\n\n\n\n\n\n\nhold LEFT on start:\n -> force "
log("Press A to start\n\n\n\n\nhold LEFT on start:\n -> force "
"cable\n\nhold RIGHT on start:\n -> force wireless\n\nhold UP on "
"start:\n -> force wireless server\n\nhold DOWN on start:\n -> force "
"wireless client");
"wireless client\n\nhold B on start:\n -> set 2 players (wireless)");
waitFor(KEY_A);
u16 initialKeys = ~REG_KEYS & KEY_ANY;
bool forceCable = initialKeys & KEY_LEFT;
Expand All @@ -37,9 +37,25 @@ int main() {
: forceWirelessServer ? LinkUniversal::Protocol::WIRELESS_SERVER
: forceWirelessClient ? LinkUniversal::Protocol::WIRELESS_CLIENT
: LinkUniversal::Protocol::AUTODETECT;
u32 maxPlayers = (initialKeys & KEY_B) ? 2 : LINK_UNIVERSAL_MAX_PLAYERS;

// (1) Create a LinkUniversal instance
linkUniversal = new LinkUniversal(protocol);
linkUniversal = new LinkUniversal(
protocol, "LinkUNI",
(LinkUniversal::CableOptions){
.baudRate = LinkCable::BAUD_RATE_1,
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
.remoteTimeout = LINK_CABLE_DEFAULT_REMOTE_TIMEOUT,
.interval = LINK_CABLE_DEFAULT_INTERVAL,
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
(LinkUniversal::WirelessOptions){
.retransmission = true,
.maxPlayers = maxPlayers,
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
.remoteTimeout = LINK_WIRELESS_DEFAULT_REMOTE_TIMEOUT,
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
.asyncACKTimerId = LINK_WIRELESS_DEFAULT_ASYNC_ACK_TIMER_ID});

// (2) Add the required interrupt service routines
interrupt_init();
Expand Down
34 changes: 28 additions & 6 deletions examples/LinkWireless_demo/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ void connect();
void messageLoop();
void log(std::string text);
void waitFor(u16 key);
void wait(u32 verticalLines);
void hang();

LinkWireless::Error lastError;
Expand Down Expand Up @@ -198,13 +199,18 @@ void connect() {
return;
} else {
std::string str = "Press START to connect\n(first ID will be used)\n\n";
for (u32 i = 0; i < LINK_WIRELESS_MAX_SERVERS; i++) {
for (u32 i = 0; i < 3; i++) {
auto server = servers[i];
if (server.id == LINK_WIRELESS_END)
break;

str += std::to_string(server.id) + " (" + std::to_string(server.gameId) +
")\n";
str +=
std::to_string(server.id) +
(server.isFull() ? " [full]"
: " [" + std::to_string(server.currentPlayerCount) +
" online]") +
"\n";
str += " -> gameID: " + std::to_string(server.gameId) + "\n";
if (server.gameName.length() > 0)
str += " -> game: " + server.gameName + "\n";
if (server.userName.length() > 0)
Expand Down Expand Up @@ -308,10 +314,10 @@ void messageLoop() {
sending = false;

// (7) Receive data
LinkWireless::Message messages[LINK_WIRELESS_MAX_TRANSFER_LENGTH];
LinkWireless::Message messages[LINK_WIRELESS_QUEUE_SIZE];
linkWireless->receive(messages);
if (messages[0].packetId != LINK_WIRELESS_END) {
for (u32 i = 0; i < LINK_WIRELESS_MAX_TRANSFER_LENGTH; i++) {
for (u32 i = 0; i < LINK_WIRELESS_QUEUE_SIZE; i++) {
auto message = messages[i];
if (message.packetId == LINK_WIRELESS_END)
break;
Expand Down Expand Up @@ -419,14 +425,18 @@ void messageLoop() {
#ifndef PROFILING_ENABLED
if (lostPackets > 0) {
output += "\n\n_lostPackets: " + std::to_string(lostPackets) + "\n";
output += "_last: (" + std::to_string(lastLostPacketPlayerId) + "->" +
output += "_last: (" + std::to_string(lastLostPacketPlayerId) + ":" +
std::to_string(lastLostPacketReceivedPacketId) + ") " +
std::to_string(lastLostPacketReceived) + " [vs " +
std::to_string(lastLostPacketExpected) + "]";
}
#endif
}

// Test lag
if (keys & KEY_DOWN)
wait(9000);

// Print
VBlankIntrWait();
log(output);
Expand All @@ -446,6 +456,18 @@ void waitFor(u16 key) {
} while (!(keys & key));
}

void wait(u32 verticalLines) {
u32 count = 0;
u32 vCount = REG_VCOUNT;

while (count < verticalLines) {
if (REG_VCOUNT != vCount) {
count++;
vCount = REG_VCOUNT;
}
};
}

void hang() {
waitFor(KEY_DOWN);
}
2 changes: 2 additions & 0 deletions examples/compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@ cp LinkSPI_demo.gba ../
cd ..

cd LinkUniversal_basic/
sed -i -e "s/\/\/ #define LINK_WIRELESS_PUT_ISR_IN_IWRAM/#define LINK_WIRELESS_PUT_ISR_IN_IWRAM/g" ../../lib/LinkWireless.hpp
make rebuild
cp LinkUniversal_basic.gba ../
sed -i -e "s/#define LINK_WIRELESS_PUT_ISR_IN_IWRAM/\/\/ #define LINK_WIRELESS_PUT_ISR_IN_IWRAM/g" ../../lib/LinkWireless.hpp
cd ..

cd LinkCable_full/
Expand Down
2 changes: 1 addition & 1 deletion lib/LinkCable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
#define LINK_CABLE_BIT_GENERAL_PURPOSE_HIGH 15
#define LINK_CABLE_BARRIER asm volatile("" ::: "memory")

static volatile char LINK_CABLE_VERSION[] = "LinkCable/v6.0.2";
static volatile char LINK_CABLE_VERSION[] = "LinkCable/v6.0.3";

void LINK_CABLE_ISR_VBLANK();
void LINK_CABLE_ISR_SERIAL();
Expand Down
2 changes: 1 addition & 1 deletion lib/LinkCableMultiboot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
return error(FAILURE_DURING_HANDSHAKE);

static volatile char LINK_CABLE_MULTIBOOT_VERSION[] =
"LinkCableMultiboot/v6.0.2";
"LinkCableMultiboot/v6.0.3";

const u8 LINK_CABLE_MULTIBOOT_CLIENT_IDS[] = {0b0010, 0b0100, 0b1000};

Expand Down
2 changes: 1 addition & 1 deletion lib/LinkGPIO.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
else \
REG &= ~(1 << BIT);

static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v6.0.2";
static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v6.0.3";

const u8 LINK_GPIO_DATA_BITS[] = {2, 3, 1, 0};
const u8 LINK_GPIO_DIRECTION_BITS[] = {6, 7, 5, 4};
Expand Down
2 changes: 1 addition & 1 deletion lib/LinkSPI.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
#define LINK_SPI_BIT_GENERAL_PURPOSE_LOW 14
#define LINK_SPI_BIT_GENERAL_PURPOSE_HIGH 15

static volatile char LINK_SPI_VERSION[] = "LinkSPI/v6.0.2";
static volatile char LINK_SPI_VERSION[] = "LinkSPI/v6.0.3";

class LinkSPI {
public:
Expand Down
29 changes: 9 additions & 20 deletions lib/LinkUniversal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,22 @@

#include <tonc_bios.h>
#include <tonc_core.h>
#include <tonc_math.h>
#include "LinkCable.hpp"
#include "LinkWireless.hpp"

#define LINK_UNIVERSAL_MAX_PLAYERS LINK_CABLE_MAX_PLAYERS
#define LINK_UNIVERSAL_DISCONNECTED LINK_CABLE_DISCONNECTED
#define LINK_UNIVERSAL_NO_DATA LINK_CABLE_NO_DATA
#define LINK_UNIVERSAL_MAX_ROOM_NUMBER 32000
#define LINK_UNIVERSAL_FULL_ROOM_NUMBER 32001
#define LINK_UNIVERSAL_FULL_ROOM_NUMBER_STR "32001"
#define LINK_UNIVERSAL_INIT_WAIT_FRAMES 10
#define LINK_UNIVERSAL_SWITCH_WAIT_FRAMES 25
#define LINK_UNIVERSAL_SWITCH_WAIT_FRAMES_RANDOM 10
#define LINK_UNIVERSAL_BROADCAST_SEARCH_WAIT_FRAMES 10
#define LINK_UNIVERSAL_SERVE_WAIT_FRAMES 60
#define LINK_UNIVERSAL_SERVE_WAIT_FRAMES_RANDOM 30

static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v6.0.2";
static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v6.0.3";

void LINK_UNIVERSAL_ISR_VBLANK();
void LINK_UNIVERSAL_ISR_SERIAL();
Expand Down Expand Up @@ -101,15 +100,16 @@ class LinkUniversal {
LINK_CABLE_DEFAULT_REMOTE_TIMEOUT, LINK_CABLE_DEFAULT_INTERVAL,
LINK_CABLE_DEFAULT_SEND_TIMER_ID},
WirelessOptions wirelessOptions = WirelessOptions{
true, LINK_WIRELESS_MAX_PLAYERS, LINK_WIRELESS_DEFAULT_TIMEOUT,
true, LINK_UNIVERSAL_MAX_PLAYERS, LINK_WIRELESS_DEFAULT_TIMEOUT,
LINK_WIRELESS_DEFAULT_REMOTE_TIMEOUT, LINK_WIRELESS_DEFAULT_INTERVAL,
LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
LINK_WIRELESS_DEFAULT_ASYNC_ACK_TIMER_ID}) {
this->linkCable = new LinkCable(
cableOptions.baudRate, cableOptions.timeout, cableOptions.remoteTimeout,
cableOptions.interval, cableOptions.sendTimerId);
this->linkWireless = new LinkWireless(
wirelessOptions.retransmission, true, wirelessOptions.maxPlayers,
wirelessOptions.retransmission, true,
min(wirelessOptions.maxPlayers, LINK_UNIVERSAL_MAX_PLAYERS),
wirelessOptions.timeout, wirelessOptions.remoteTimeout,
wirelessOptions.interval, wirelessOptions.sendTimerId,
wirelessOptions.asyncACKTimerId);
Expand Down Expand Up @@ -210,15 +210,6 @@ class LinkUniversal {
}

receiveWirelessMessages();

if (!linkWireless->_hasActiveAsyncCommand() &&
linkWireless->playerCount() == linkWireless->config.maxPlayers &&
!didCloseWirelessRoom) {
linkWireless->serve(config.gameName,
LINK_UNIVERSAL_FULL_ROOM_NUMBER_STR,
LINK_WIRELESS_MAX_GAME_ID, true);
didCloseWirelessRoom = true;
}
}

break;
Expand Down Expand Up @@ -316,7 +307,6 @@ class LinkUniversal {
u32 switchWait = 0;
u32 subWaitCount = 0;
u32 serveWait = 0;
bool didCloseWirelessRoom = false;
volatile bool isEnabled = false;

void receiveCableMessages() {
Expand All @@ -327,10 +317,10 @@ class LinkUniversal {
}

void receiveWirelessMessages() {
LinkWireless::Message messages[LINK_WIRELESS_MAX_TRANSFER_LENGTH];
LinkWireless::Message messages[LINK_WIRELESS_QUEUE_SIZE];
linkWireless->receive(messages);

for (u32 i = 0; i < LINK_WIRELESS_MAX_TRANSFER_LENGTH; i++) {
for (u32 i = 0; i < LINK_WIRELESS_QUEUE_SIZE; i++) {
auto message = messages[i];
if (message.packetId == LINK_WIRELESS_END)
break;
Expand Down Expand Up @@ -393,10 +383,10 @@ class LinkUniversal {
if (server.id == LINK_WIRELESS_END)
break;

if (server.gameName == config.gameName) {
if (!server.isFull() && server.gameName == config.gameName) {
u32 randomNumber = safeStoi(server.userName);
if (randomNumber > maxRandomNumber &&
randomNumber < LINK_UNIVERSAL_FULL_ROOM_NUMBER) {
randomNumber < LINK_UNIVERSAL_MAX_ROOM_NUMBER) {
maxRandomNumber = randomNumber;
serverIndex = i;
}
Expand Down Expand Up @@ -491,7 +481,6 @@ class LinkUniversal {
serveWait = 0;
for (u32 i = 0; i < LINK_UNIVERSAL_MAX_PLAYERS; i++)
incomingMessages[i].clear();
didCloseWirelessRoom = false;
}

u32 safeStoi(const std::string& str) {
Expand Down
Loading

0 comments on commit cadda9e

Please sign in to comment.