mirror of
https://github.com/afska/gba-link-connection.git
synced 2026-10-03 06:17:07 -05:00
1050 lines
28 KiB
C++
1050 lines
28 KiB
C++
#ifndef LINK_WIRELESS_H
|
|
#define LINK_WIRELESS_H
|
|
|
|
// --------------------------------------------------------------------------
|
|
// A high level driver for the GBA Wireless Adapter.
|
|
// --------------------------------------------------------------------------
|
|
// Usage:
|
|
// - 1) Include this header in your main.cpp file and add:
|
|
// LinkWireless* linkWireless = new LinkWireless();
|
|
// - 2) Initialize the library with:
|
|
// linkWireless->activate();
|
|
// - 3) Start a server:
|
|
// linkWireless->serve();
|
|
// // `getState()` should return SERVING now...
|
|
// // `getPlayerId()` should return 0
|
|
// // `getPlayerCount()` should reflect the number of active consoles
|
|
// // call `acceptConnections()` periodically
|
|
// - 4) Connect to a server:
|
|
// std::vector<LinkWireless::Server> servers;
|
|
// linkWireless->getServers(servers);
|
|
// linkWireless->connect(servers[0].id);
|
|
// while (linkWireless->getState() == LinkWireless::State::CONNECTING)
|
|
// linkWireless->keepConnecting();
|
|
// // `getState()` should return CONNECTED now...
|
|
// // `getPlayerId()` should return 1, 2, 3, or 4 (the host is 0)
|
|
// - 5) Send data:
|
|
// linkConnection->send(std::vector<u32>{1, 2, 3});
|
|
// - 6) Receive data:
|
|
// std::vector<LinkWireless::Message> messages;
|
|
// linkConnection->receive(messages);
|
|
// if (messages.size() > 0) {
|
|
// // ...
|
|
// }
|
|
// - 7) Disconnect:
|
|
// linkWireless->disconnect();
|
|
// --------------------------------------------------------------------------
|
|
// restrictions:
|
|
// - servers can send up to 19 words of 32 bits at a time!
|
|
// - clients can send up to 3 words of 32 bits at a time!
|
|
// - if retransmission is on, these limits drop to 14 and 1!
|
|
// - you can workaround these limits by doing multiple exchanges with
|
|
// receiveMany(...)!
|
|
// --------------------------------------------------------------------------
|
|
|
|
#include <tonc_core.h>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "LinkGPIO.h"
|
|
#include "LinkSPI.h"
|
|
|
|
#define LINK_WIRELESS_DEFAULT_MSG_TIMEOUT 5
|
|
#define LINK_WIRELESS_DEFAULT_MULTIRECEIVE_TIMEOUT ((160 + 68) * 5)
|
|
#define LINK_WIRELESS_DEFAULT_BUFFER_SIZE 30
|
|
#define LINK_WIRELESS_MSG_CONFIRMATION 0
|
|
#define LINK_WIRELESS_PING_WAIT 50
|
|
#define LINK_WIRELESS_TRANSFER_WAIT 15
|
|
#define LINK_WIRELESS_BROADCAST_SEARCH_WAIT_FRAMES 60
|
|
#define LINK_WIRELESS_CMD_TIMEOUT 100
|
|
#define LINK_WIRELESS_MIN_PLAYERS 2
|
|
#define LINK_WIRELESS_MAX_PLAYERS 5
|
|
#define LINK_WIRELESS_MAX_GAME_NAME_LENGTH 14
|
|
#define LINK_WIRELESS_MAX_USER_NAME_LENGTH 8
|
|
#define LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH 20
|
|
#define LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH 4
|
|
#define LINK_WIRELESS_LOGIN_STEPS 9
|
|
#define LINK_WIRELESS_COMMAND_HEADER 0x9966
|
|
#define LINK_WIRELESS_RESPONSE_ACK 0x80
|
|
#define LINK_WIRELESS_DATA_REQUEST 0x80000000
|
|
#define LINK_WIRELESS_SETUP_MAGIC 0x003c0420
|
|
#define LINK_WIRELESS_STILL_CONNECTING 0x01000000
|
|
#define LINK_WIRELESS_BROADCAST_LENGTH 6
|
|
#define LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH \
|
|
(1 + LINK_WIRELESS_BROADCAST_LENGTH)
|
|
#define LINK_WIRELESS_COMMAND_HELLO 0x10
|
|
#define LINK_WIRELESS_COMMAND_SETUP 0x17
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST 0x16
|
|
#define LINK_WIRELESS_COMMAND_START_HOST 0x19
|
|
#define LINK_WIRELESS_COMMAND_ACCEPT_CONNECTIONS 0x1a
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST_READ_START 0x1c
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST_READ_POLL 0x1d
|
|
#define LINK_WIRELESS_COMMAND_BROADCAST_READ_END 0x1e
|
|
#define LINK_WIRELESS_COMMAND_CONNECT 0x1f
|
|
#define LINK_WIRELESS_COMMAND_IS_FINISHED_CONNECT 0x20
|
|
#define LINK_WIRELESS_COMMAND_FINISH_CONNECTION 0x21
|
|
#define LINK_WIRELESS_COMMAND_SEND_DATA 0x24
|
|
#define LINK_WIRELESS_COMMAND_RECEIVE_DATA 0x26
|
|
#define LINK_WIRELESS_COMMAND_DISCONNECT 0x30
|
|
|
|
#define LINK_WIRELESS_RESET_IF_NEEDED \
|
|
if (!isEnabled) \
|
|
return false; \
|
|
if (state == NEEDS_RESET) \
|
|
if (!reset()) \
|
|
return false;
|
|
|
|
static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v4.3.0";
|
|
|
|
const u16 LINK_WIRELESS_LOGIN_PARTS[] = {0x494e, 0x494e, 0x544e, 0x544e, 0x4e45,
|
|
0x4e45, 0x4f44, 0x4f44, 0x8001};
|
|
const u16 LINK_WIRELESS_USER_MAX_SERVER_TRANSFER_LENGTHS[] = {19, 14};
|
|
const u32 LINK_WIRELESS_USER_MAX_CLIENT_TRANSFER_LENGTHS[] = {3, 1};
|
|
|
|
class LinkWireless {
|
|
public:
|
|
enum State {
|
|
NEEDS_RESET,
|
|
AUTHENTICATED,
|
|
SEARCHING,
|
|
SERVING,
|
|
CONNECTING,
|
|
CONNECTED
|
|
};
|
|
|
|
enum Error {
|
|
// User errors
|
|
NONE = 0,
|
|
WRONG_STATE = 1,
|
|
GAME_NAME_TOO_LONG = 2,
|
|
USER_NAME_TOO_LONG = 3,
|
|
INVALID_SEND_SIZE = 4,
|
|
BUFFER_IS_FULL = 5,
|
|
RETRANSMISSION_IS_OFF = 6,
|
|
// Communication errors
|
|
COMMAND_FAILED = 7,
|
|
WEIRD_PLAYER_ID = 8,
|
|
SEND_DATA_FAILED = 9,
|
|
RECEIVE_DATA_FAILED = 10,
|
|
BAD_CONFIRMATION = 11,
|
|
BAD_MESSAGE = 12,
|
|
MAX_PLAYERS_LIMIT_REACHED = 13,
|
|
TIMEOUT = 14
|
|
};
|
|
|
|
struct Message {
|
|
u8 playerId = 0;
|
|
std::vector<u32> data = std::vector<u32>{};
|
|
|
|
u32 _packetId = 0;
|
|
};
|
|
|
|
struct Server {
|
|
u16 id;
|
|
std::string gameName;
|
|
std::string userName;
|
|
};
|
|
|
|
explicit LinkWireless(
|
|
bool forwarding = true,
|
|
bool retransmission = true,
|
|
u8 maxPlayers = LINK_WIRELESS_MAX_PLAYERS,
|
|
u32 msgTimeout = LINK_WIRELESS_DEFAULT_MSG_TIMEOUT,
|
|
u32 multiReceiveTimeout = LINK_WIRELESS_DEFAULT_MULTIRECEIVE_TIMEOUT,
|
|
u32 bufferSize = LINK_WIRELESS_DEFAULT_BUFFER_SIZE) {
|
|
this->forwarding = forwarding;
|
|
this->retransmission = retransmission;
|
|
this->maxPlayers = maxPlayers;
|
|
this->msgTimeout = msgTimeout;
|
|
this->multiReceiveTimeout = multiReceiveTimeout;
|
|
this->bufferSize = bufferSize;
|
|
}
|
|
|
|
bool isActive() { return isEnabled; }
|
|
|
|
bool activate() {
|
|
lastError = NONE;
|
|
bool success = reset();
|
|
|
|
isEnabled = true;
|
|
return success;
|
|
}
|
|
|
|
void deactivate() {
|
|
lastError = NONE;
|
|
isEnabled = false;
|
|
stop();
|
|
}
|
|
|
|
bool serve(std::string gameName = "", std::string userName = "") {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != AUTHENTICATED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
if (gameName.length() > LINK_WIRELESS_MAX_GAME_NAME_LENGTH) {
|
|
lastError = GAME_NAME_TOO_LONG;
|
|
return false;
|
|
}
|
|
if (userName.length() > LINK_WIRELESS_MAX_GAME_NAME_LENGTH) {
|
|
lastError = USER_NAME_TOO_LONG;
|
|
return false;
|
|
}
|
|
gameName.append(LINK_WIRELESS_MAX_GAME_NAME_LENGTH - gameName.length(), 0);
|
|
userName.append(LINK_WIRELESS_MAX_USER_NAME_LENGTH - userName.length(), 0);
|
|
|
|
auto broadcast = std::vector<u32>{
|
|
buildU32(buildU16(gameName[1], gameName[0]), buildU16(0x02, 0x02)),
|
|
buildU32(buildU16(gameName[5], gameName[4]),
|
|
buildU16(gameName[3], gameName[2])),
|
|
buildU32(buildU16(gameName[9], gameName[8]),
|
|
buildU16(gameName[7], gameName[6])),
|
|
buildU32(buildU16(gameName[13], gameName[12]),
|
|
buildU16(gameName[11], gameName[10])),
|
|
buildU32(buildU16(userName[3], userName[2]),
|
|
buildU16(userName[1], userName[0])),
|
|
buildU32(buildU16(userName[7], userName[6]),
|
|
buildU16(userName[5], userName[4]))};
|
|
|
|
bool success =
|
|
sendCommand(LINK_WIRELESS_COMMAND_BROADCAST, broadcast).success &&
|
|
sendCommand(LINK_WIRELESS_COMMAND_START_HOST).success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
state = SERVING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool acceptConnections() {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != SERVING) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
auto result = sendCommand(LINK_WIRELESS_COMMAND_ACCEPT_CONNECTIONS);
|
|
|
|
if (!result.success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
playerCount = 1 + result.responses.size();
|
|
|
|
if (playerCount > maxPlayers) {
|
|
disconnect();
|
|
lastError = MAX_PLAYERS_LIMIT_REACHED;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getServers(std::vector<Server>& servers) {
|
|
return getServers(servers, []() {});
|
|
}
|
|
|
|
template <typename F>
|
|
bool getServers(std::vector<Server>& servers, F onWait) {
|
|
if (!getServersAsyncStart())
|
|
return false;
|
|
|
|
waitVBlanks(LINK_WIRELESS_BROADCAST_SEARCH_WAIT_FRAMES, onWait);
|
|
|
|
if (!getServersAsyncEnd(servers))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getServersAsyncStart() {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != AUTHENTICATED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
bool success =
|
|
sendCommand(LINK_WIRELESS_COMMAND_BROADCAST_READ_START).success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
state = SEARCHING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool getServersAsyncEnd(std::vector<Server>& servers) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != SEARCHING) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
auto result = sendCommand(LINK_WIRELESS_COMMAND_BROADCAST_READ_POLL);
|
|
bool success1 =
|
|
result.success &&
|
|
result.responses.size() % LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH == 0;
|
|
|
|
if (!success1) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
bool success2 =
|
|
sendCommand(LINK_WIRELESS_COMMAND_BROADCAST_READ_END).success;
|
|
|
|
if (!success2) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
u32 totalBroadcasts =
|
|
result.responses.size() / LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH;
|
|
|
|
for (u32 i = 0; i < totalBroadcasts; i++) {
|
|
u32 start = LINK_WIRELESS_BROADCAST_RESPONSE_LENGTH * i;
|
|
|
|
Server server;
|
|
server.id = (u16)result.responses[start];
|
|
recoverName(server.gameName, result.responses[start + 1], false);
|
|
recoverName(server.gameName, result.responses[start + 2]);
|
|
recoverName(server.gameName, result.responses[start + 3]);
|
|
recoverName(server.gameName, result.responses[start + 4]);
|
|
recoverName(server.userName, result.responses[start + 5]);
|
|
recoverName(server.userName, result.responses[start + 6]);
|
|
|
|
servers.push_back(server);
|
|
}
|
|
|
|
state = AUTHENTICATED;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool connect(u16 serverId) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != AUTHENTICATED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
bool success =
|
|
sendCommand(LINK_WIRELESS_COMMAND_CONNECT, std::vector<u32>{serverId})
|
|
.success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
state = CONNECTING;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool keepConnecting() {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != CONNECTING) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
auto result1 = sendCommand(LINK_WIRELESS_COMMAND_IS_FINISHED_CONNECT);
|
|
if (!result1.success || result1.responses.size() == 0) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
if (result1.responses[0] == LINK_WIRELESS_STILL_CONNECTING)
|
|
return true;
|
|
|
|
u8 assignedPlayerId = 1 + (u8)msB32(result1.responses[0]);
|
|
u16 assignedClientId = (u16)result1.responses[0];
|
|
|
|
if (assignedPlayerId >= LINK_WIRELESS_MAX_PLAYERS) {
|
|
reset();
|
|
lastError = WEIRD_PLAYER_ID;
|
|
return false;
|
|
}
|
|
|
|
auto result2 = sendCommand(LINK_WIRELESS_COMMAND_FINISH_CONNECTION);
|
|
if (!result2.success || result2.responses.size() == 0 ||
|
|
(u16)result2.responses[0] != assignedClientId) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
playerId = assignedPlayerId;
|
|
state = CONNECTED;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool send(std::vector<u32> data, int _author = -1) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != SERVING && state != CONNECTED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
u32 maxTransferLength =
|
|
state == SERVING
|
|
? LINK_WIRELESS_USER_MAX_SERVER_TRANSFER_LENGTHS[retransmission]
|
|
: LINK_WIRELESS_USER_MAX_CLIENT_TRANSFER_LENGTHS[retransmission];
|
|
if (data.size() == 0 || data.size() > maxTransferLength) {
|
|
lastError = INVALID_SEND_SIZE;
|
|
return false;
|
|
}
|
|
|
|
if (outgoingMessages.size() >= bufferSize) {
|
|
lastError = BUFFER_IS_FULL;
|
|
return false;
|
|
}
|
|
|
|
Message message;
|
|
message.playerId = _author < 0 ? playerId : _author;
|
|
message.data = data;
|
|
message._packetId = ++lastPacketId;
|
|
|
|
outgoingMessages.push_back(message);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool receive(std::vector<Message>& messages, bool _enableTimeouts = true) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != SERVING && state != CONNECTED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
if (!sendPendingMessages()) {
|
|
lastError = SEND_DATA_FAILED;
|
|
return false;
|
|
}
|
|
|
|
std::vector<u32> words;
|
|
if (!receiveData(words)) {
|
|
lastError = RECEIVE_DATA_FAILED;
|
|
return false;
|
|
}
|
|
|
|
if (_enableTimeouts)
|
|
trackTimeouts();
|
|
|
|
u32 startIndex = messages.size();
|
|
|
|
for (u32 i = 0; i < words.size(); i++) {
|
|
MessageHeaderSerializer serializer;
|
|
serializer.asInt = words[i];
|
|
|
|
MessageHeader header = serializer.asStruct;
|
|
u8 remotePlayerCount = LINK_WIRELESS_MIN_PLAYERS + header.clientCount;
|
|
u8 remotePlayerId = header.playerId;
|
|
u8 size = header.size;
|
|
u32 packetId = header.packetId;
|
|
|
|
if (i + size >= words.size()) {
|
|
reset();
|
|
lastError = BAD_MESSAGE;
|
|
return false;
|
|
}
|
|
|
|
timeouts[0] = 0;
|
|
timeouts[remotePlayerId] = 0;
|
|
|
|
if (state == SERVING) {
|
|
if (retransmission && packetId != LINK_WIRELESS_MSG_CONFIRMATION &&
|
|
lastPacketIdFromClients[remotePlayerId] > 0 &&
|
|
packetId != lastPacketIdFromClients[remotePlayerId] + 1)
|
|
goto skip;
|
|
|
|
if (packetId != LINK_WIRELESS_MSG_CONFIRMATION)
|
|
lastPacketIdFromClients[remotePlayerId] = packetId;
|
|
} else {
|
|
if (retransmission && packetId != LINK_WIRELESS_MSG_CONFIRMATION &&
|
|
lastPacketIdFromServer > 0 &&
|
|
packetId != lastPacketIdFromServer + 1)
|
|
goto skip;
|
|
|
|
playerCount = remotePlayerCount;
|
|
|
|
if (packetId != LINK_WIRELESS_MSG_CONFIRMATION)
|
|
lastPacketIdFromServer = packetId;
|
|
}
|
|
|
|
if (remotePlayerId == playerId) {
|
|
skip:
|
|
i += size;
|
|
continue;
|
|
}
|
|
|
|
if (size > 0) {
|
|
Message message;
|
|
message.playerId = remotePlayerId;
|
|
for (u32 j = 0; j < size; j++)
|
|
message.data.push_back(words[i + 1 + j]);
|
|
message._packetId = packetId;
|
|
|
|
if (retransmission && packetId == LINK_WIRELESS_MSG_CONFIRMATION) {
|
|
if (!handleConfirmation(message)) {
|
|
reset();
|
|
lastError = BAD_CONFIRMATION;
|
|
return false;
|
|
}
|
|
} else {
|
|
messages.push_back(message);
|
|
}
|
|
|
|
i += size;
|
|
}
|
|
}
|
|
|
|
if (_enableTimeouts && !checkTimeouts())
|
|
return false;
|
|
|
|
if (state == SERVING && forwarding && playerCount > 2) {
|
|
for (u32 i = startIndex; i < messages.size(); i++) {
|
|
auto message = messages[i];
|
|
send(message.data, message.playerId);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool receiveMany(std::vector<Message>& messages, u32 times) {
|
|
return receiveMany(messages, times, []() { return false; });
|
|
}
|
|
|
|
template <typename F>
|
|
bool receiveMany(std::vector<Message>& messages, u32 times, F cancel) {
|
|
if (!retransmission) {
|
|
lastError = RETRANSMISSION_IS_OFF;
|
|
return false;
|
|
}
|
|
|
|
u32 successfulExchanges = 0;
|
|
trackTimeouts();
|
|
|
|
u32 lines = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
while (successfulExchanges < times) {
|
|
if (cancel())
|
|
return true;
|
|
|
|
if (timeout(multiReceiveTimeout, lines, vCount)) {
|
|
lastError = TIMEOUT;
|
|
disconnect();
|
|
return false;
|
|
}
|
|
|
|
if (!receive(messages, false))
|
|
return false;
|
|
|
|
if (didReceiveAnyBytes)
|
|
successfulExchanges++;
|
|
}
|
|
|
|
if (!checkTimeouts()) {
|
|
lastError = TIMEOUT;
|
|
disconnect();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool disconnect() {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
|
|
bool success = sendCommand(LINK_WIRELESS_COMMAND_DISCONNECT).success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
return false;
|
|
}
|
|
|
|
reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
State getState() { return state; }
|
|
u8 getPlayerId() { return playerId; }
|
|
u8 getPlayerCount() { return playerCount; }
|
|
bool canSend() { return outgoingMessages.size() < bufferSize; }
|
|
u32 getPendingCount() { return outgoingMessages.size(); }
|
|
bool didReceiveBytes() { return didReceiveAnyBytes; }
|
|
Error getLastError() {
|
|
Error error = lastError;
|
|
lastError = NONE;
|
|
return error;
|
|
}
|
|
|
|
~LinkWireless() {
|
|
delete linkSPI;
|
|
delete linkGPIO;
|
|
}
|
|
|
|
private:
|
|
struct LoginMemory {
|
|
u16 previousGBAData = 0xffff;
|
|
u16 previousAdapterData = 0xffff;
|
|
};
|
|
|
|
struct CommandResult {
|
|
bool success = false;
|
|
std::vector<u32> responses = std::vector<u32>{};
|
|
};
|
|
|
|
struct MessageHeader {
|
|
unsigned int packetId : 22;
|
|
unsigned int size : 5;
|
|
unsigned int playerId : 3;
|
|
unsigned int clientCount : 2;
|
|
};
|
|
|
|
union MessageHeaderSerializer {
|
|
MessageHeader asStruct;
|
|
u32 asInt;
|
|
};
|
|
|
|
bool forwarding;
|
|
bool retransmission;
|
|
u8 maxPlayers;
|
|
u32 msgTimeout;
|
|
u32 multiReceiveTimeout;
|
|
u32 bufferSize;
|
|
LinkSPI* linkSPI = new LinkSPI();
|
|
LinkGPIO* linkGPIO = new LinkGPIO();
|
|
State state = NEEDS_RESET;
|
|
u8 playerId = 0;
|
|
u8 playerCount = 1;
|
|
std::vector<Message> outgoingMessages;
|
|
u32 lastPacketId = 0;
|
|
u32 lastPacketIdFromServer = 0;
|
|
u32 lastConfirmationFromServer = 0;
|
|
u32 lastPacketIdFromClients[LINK_WIRELESS_MAX_PLAYERS];
|
|
u32 lastConfirmationFromClients[LINK_WIRELESS_MAX_PLAYERS];
|
|
u32 timeouts[LINK_WIRELESS_MAX_PLAYERS];
|
|
bool didReceiveAnyBytes = false;
|
|
Error lastError = NONE;
|
|
bool isEnabled = false;
|
|
|
|
bool sendPendingMessages() {
|
|
if (outgoingMessages.empty() && !retransmission) {
|
|
Message emptyMessage;
|
|
emptyMessage.playerId = playerId;
|
|
emptyMessage._packetId = ++lastPacketId;
|
|
outgoingMessages.push_back(emptyMessage);
|
|
}
|
|
|
|
u32 maxTransferLength = getDeviceTransferLength();
|
|
std::vector<u32> words;
|
|
|
|
if (retransmission)
|
|
addConfirmations(words);
|
|
|
|
for (auto& message : outgoingMessages) {
|
|
u8 size = message.data.size();
|
|
u32 header =
|
|
buildMessageHeader(message.playerId, size, message._packetId);
|
|
|
|
if (words.size() + 1 + size > maxTransferLength)
|
|
break;
|
|
|
|
words.push_back(header);
|
|
words.insert(words.end(), message.data.begin(), message.data.end());
|
|
}
|
|
|
|
if (!sendData(words))
|
|
return false;
|
|
|
|
if (!retransmission)
|
|
outgoingMessages.clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
void trackTimeouts() {
|
|
for (u32 i = 0; i < playerCount; i++)
|
|
if (i != playerId)
|
|
timeouts[i]++;
|
|
}
|
|
|
|
bool checkTimeouts() {
|
|
for (u32 i = 0; i < playerCount; i++) {
|
|
if ((i == 0 || state == SERVING) && timeouts[i] > msgTimeout) {
|
|
lastError = TIMEOUT;
|
|
disconnect();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void addConfirmations(std::vector<u32>& words) {
|
|
if (state == SERVING) {
|
|
words.push_back(buildConfirmationHeader(0));
|
|
for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++)
|
|
words.push_back(lastPacketIdFromClients[1 + i]);
|
|
} else {
|
|
words.push_back(buildConfirmationHeader(playerId));
|
|
words.push_back(lastPacketIdFromServer);
|
|
}
|
|
}
|
|
|
|
bool handleConfirmation(Message confirmation) {
|
|
if (confirmation.data.size() == 0)
|
|
return false;
|
|
|
|
bool isServerConfirmation = confirmation.playerId == 0;
|
|
|
|
if (isServerConfirmation) {
|
|
if (state != CONNECTED ||
|
|
confirmation.data.size() != LINK_WIRELESS_MAX_PLAYERS - 1)
|
|
return false;
|
|
|
|
lastConfirmationFromServer = confirmation.data[playerId - 1];
|
|
removeConfirmedMessages(lastConfirmationFromServer);
|
|
} else {
|
|
if (state != SERVING || confirmation.data.size() != 1)
|
|
return false;
|
|
|
|
u32 confirmationData = confirmation.data[0];
|
|
lastConfirmationFromClients[confirmation.playerId] = confirmationData;
|
|
|
|
u32 min = 0xffffffff;
|
|
for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS - 1; i++) {
|
|
u32 confirmationData = lastConfirmationFromClients[1 + i];
|
|
if (confirmationData > 0 && confirmationData < min)
|
|
min = confirmationData;
|
|
}
|
|
if (min < 0xffffffff)
|
|
removeConfirmedMessages(min);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void removeConfirmedMessages(u32 confirmation) {
|
|
outgoingMessages.erase(
|
|
std::remove_if(outgoingMessages.begin(), outgoingMessages.end(),
|
|
[confirmation](Message it) {
|
|
return it._packetId <= confirmation;
|
|
}),
|
|
outgoingMessages.end());
|
|
}
|
|
|
|
u32 buildConfirmationHeader(u8 playerId) {
|
|
return buildMessageHeader(
|
|
playerId, playerId == 0 ? LINK_WIRELESS_MAX_PLAYERS - 1 : 1, 0);
|
|
}
|
|
|
|
u32 buildMessageHeader(u8 playerId, u8 size, u32 packetId) {
|
|
MessageHeader header;
|
|
header.clientCount = playerCount - LINK_WIRELESS_MIN_PLAYERS;
|
|
header.playerId = playerId;
|
|
header.size = size;
|
|
header.packetId = packetId;
|
|
|
|
MessageHeaderSerializer serializer;
|
|
serializer.asStruct = header;
|
|
return serializer.asInt;
|
|
}
|
|
|
|
bool sendData(std::vector<u32> data) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != SERVING && state != CONNECTED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
if (data.size() == 0 || data.size() > getDeviceTransferLength()) {
|
|
lastError = INVALID_SEND_SIZE;
|
|
return false;
|
|
}
|
|
|
|
u32 bytes = data.size() * 4;
|
|
u32 header = playerId == 0 ? bytes : (1 << (3 + playerId * 5)) * bytes;
|
|
data.insert(data.begin(), header);
|
|
|
|
bool success = sendCommand(LINK_WIRELESS_COMMAND_SEND_DATA, data).success;
|
|
|
|
if (!success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool receiveData(std::vector<u32>& data) {
|
|
LINK_WIRELESS_RESET_IF_NEEDED
|
|
if (state != SERVING && state != CONNECTED) {
|
|
lastError = WRONG_STATE;
|
|
return false;
|
|
}
|
|
|
|
this->didReceiveAnyBytes = false;
|
|
|
|
auto result = sendCommand(LINK_WIRELESS_COMMAND_RECEIVE_DATA);
|
|
data = result.responses;
|
|
|
|
if (!result.success) {
|
|
reset();
|
|
lastError = COMMAND_FAILED;
|
|
return false;
|
|
}
|
|
|
|
if (data.size() > 0) {
|
|
data.erase(data.begin());
|
|
this->didReceiveAnyBytes = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
u32 getDeviceTransferLength() {
|
|
return state == SERVING ? LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH
|
|
: LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH;
|
|
}
|
|
|
|
void recoverName(std::string& name,
|
|
u32 word,
|
|
bool includeFirstTwoBytes = true) {
|
|
u32 character = 0;
|
|
if (includeFirstTwoBytes) {
|
|
character = lsB16(lsB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
character = msB16(lsB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
}
|
|
character = lsB16(msB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
character = msB16(msB32(word));
|
|
if (character > 0)
|
|
name.push_back(character);
|
|
}
|
|
|
|
bool reset() {
|
|
this->state = NEEDS_RESET;
|
|
this->playerId = 0;
|
|
this->playerCount = 1;
|
|
this->outgoingMessages = std::vector<Message>{};
|
|
this->lastPacketId = 0;
|
|
this->lastPacketIdFromServer = 0;
|
|
this->lastConfirmationFromServer = 0;
|
|
for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++) {
|
|
this->lastPacketIdFromClients[i] = 0;
|
|
this->lastConfirmationFromClients[i] = 0;
|
|
this->timeouts[i] = 0;
|
|
}
|
|
this->didReceiveAnyBytes = false;
|
|
|
|
stop();
|
|
return start();
|
|
}
|
|
|
|
bool start() {
|
|
pingAdapter();
|
|
linkSPI->activate(LinkSPI::Mode::MASTER_256KBPS);
|
|
|
|
if (!login())
|
|
return false;
|
|
|
|
wait(LINK_WIRELESS_TRANSFER_WAIT);
|
|
|
|
if (!sendCommand(LINK_WIRELESS_COMMAND_HELLO).success)
|
|
return false;
|
|
|
|
if (!sendCommand(LINK_WIRELESS_COMMAND_SETUP,
|
|
std::vector<u32>{LINK_WIRELESS_SETUP_MAGIC})
|
|
.success)
|
|
return false;
|
|
|
|
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
|
|
state = AUTHENTICATED;
|
|
|
|
return true;
|
|
}
|
|
|
|
void stop() { linkSPI->deactivate(); }
|
|
|
|
void pingAdapter() {
|
|
linkGPIO->setMode(LinkGPIO::Pin::SO, LinkGPIO::Direction::OUTPUT);
|
|
linkGPIO->setMode(LinkGPIO::Pin::SD, LinkGPIO::Direction::OUTPUT);
|
|
linkGPIO->writePin(LinkGPIO::SD, true);
|
|
wait(LINK_WIRELESS_PING_WAIT);
|
|
linkGPIO->writePin(LinkGPIO::SD, false);
|
|
}
|
|
|
|
bool login() {
|
|
LoginMemory memory;
|
|
|
|
if (!exchangeLoginPacket(LINK_WIRELESS_LOGIN_PARTS[0], 0, memory))
|
|
return false;
|
|
|
|
for (u32 i = 0; i < LINK_WIRELESS_LOGIN_STEPS; i++) {
|
|
if (!exchangeLoginPacket(LINK_WIRELESS_LOGIN_PARTS[i],
|
|
LINK_WIRELESS_LOGIN_PARTS[i], memory))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool exchangeLoginPacket(u16 data,
|
|
u16 expectedResponse,
|
|
LoginMemory& memory) {
|
|
u32 packet = buildU32(~memory.previousAdapterData, data);
|
|
u32 response = transfer(packet, false);
|
|
|
|
if (msB32(response) != expectedResponse ||
|
|
lsB32(response) != (u16)~memory.previousGBAData)
|
|
return false;
|
|
|
|
memory.previousGBAData = data;
|
|
memory.previousAdapterData = expectedResponse;
|
|
|
|
return true;
|
|
}
|
|
|
|
CommandResult sendCommand(u8 type,
|
|
std::vector<u32> params = std::vector<u32>{}) {
|
|
CommandResult result;
|
|
u16 length = params.size();
|
|
u32 command = buildCommand(type, length);
|
|
|
|
if (transfer(command) != LINK_WIRELESS_DATA_REQUEST)
|
|
return result;
|
|
|
|
for (auto& param : params) {
|
|
if (transfer(param) != LINK_WIRELESS_DATA_REQUEST)
|
|
return result;
|
|
}
|
|
|
|
u32 response = transfer(LINK_WIRELESS_DATA_REQUEST);
|
|
u16 header = msB32(response);
|
|
u16 data = lsB32(response);
|
|
u8 responses = msB16(data);
|
|
u8 ack = lsB16(data);
|
|
|
|
if (header != LINK_WIRELESS_COMMAND_HEADER)
|
|
return result;
|
|
if (ack != type + LINK_WIRELESS_RESPONSE_ACK)
|
|
return result;
|
|
|
|
for (u32 i = 0; i < responses; i++)
|
|
result.responses.push_back(transfer(LINK_WIRELESS_DATA_REQUEST));
|
|
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
u32 buildCommand(u8 type, u8 length = 0) {
|
|
return buildU32(LINK_WIRELESS_COMMAND_HEADER, buildU16(length, type));
|
|
}
|
|
|
|
u32 transfer(u32 data, bool customAck = true) {
|
|
if (!customAck)
|
|
wait(LINK_WIRELESS_TRANSFER_WAIT);
|
|
|
|
u32 lines = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
u32 receivedData = linkSPI->transfer(
|
|
data, [this, &lines, &vCount]() { return cmdTimeout(lines, vCount); },
|
|
false, customAck);
|
|
|
|
lines = 0;
|
|
vCount = REG_VCOUNT;
|
|
if (customAck) {
|
|
linkSPI->_setSOLow();
|
|
while (!linkSPI->_isSIHigh())
|
|
if (cmdTimeout(lines, vCount))
|
|
return LINK_SPI_NO_DATA;
|
|
linkSPI->_setSOHigh();
|
|
while (linkSPI->_isSIHigh())
|
|
if (cmdTimeout(lines, vCount))
|
|
return LINK_SPI_NO_DATA;
|
|
linkSPI->_setSOLow();
|
|
}
|
|
|
|
return receivedData;
|
|
}
|
|
|
|
bool cmdTimeout(u32& lines, u32& vCount) {
|
|
return timeout(LINK_WIRELESS_CMD_TIMEOUT, lines, vCount);
|
|
}
|
|
|
|
bool timeout(u32 limit, u32& lines, u32& vCount) {
|
|
if (REG_VCOUNT != vCount) {
|
|
lines += std::max((s32)REG_VCOUNT - (s32)vCount, 0);
|
|
vCount = REG_VCOUNT;
|
|
}
|
|
|
|
return lines > limit;
|
|
}
|
|
|
|
void wait(u32 verticalLines) {
|
|
u32 count = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
|
|
while (count < verticalLines) {
|
|
if (REG_VCOUNT != vCount) {
|
|
count += std::max((s32)REG_VCOUNT - (s32)vCount, 0);
|
|
vCount = REG_VCOUNT;
|
|
}
|
|
};
|
|
}
|
|
|
|
template <typename F>
|
|
void waitVBlanks(u32 vBlanks, F onVBlank) {
|
|
u32 count = 0;
|
|
u32 vCount = REG_VCOUNT;
|
|
|
|
while (count < vBlanks) {
|
|
if (REG_VCOUNT != vCount) {
|
|
vCount = REG_VCOUNT;
|
|
|
|
if (vCount == 160) {
|
|
onVBlank();
|
|
count++;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
u32 buildU32(u16 msB, u16 lsB) { return (msB << 16) | lsB; }
|
|
u16 buildU16(u8 msB, u8 lsB) { return (msB << 8) | lsB; }
|
|
u16 msB32(u32 value) { return value >> 16; }
|
|
u16 lsB32(u32 value) { return value & 0xffff; }
|
|
u8 msB16(u16 value) { return value >> 8; }
|
|
u8 lsB16(u16 value) { return value & 0xff; }
|
|
};
|
|
|
|
extern LinkWireless* linkWireless;
|
|
|
|
#endif // LINK_WIRELESS_H
|