mirror of
https://github.com/afska/gba-link-connection.git
synced 2026-04-25 08:07:59 -05:00
Updating README and small fixes after reviewing the whole branch
This commit is contained in:
parent
08facca76b
commit
d022fba681
29
README.md
29
README.md
|
|
@ -32,12 +32,12 @@ _(click on the emojis for documentation)_
|
|||
- **Compiled ROMs are available** in [Releases](https://github.com/afska/gba-link-connection/releases).
|
||||
- The example code uses [libtonc](https://github.com/gbadev-org/libtonc) (and [libugba](https://github.com/AntonioND/libugba) for interrupts), but any library can be used.
|
||||
- The examples can be tested on real GBAs or using emulators.
|
||||
- For `LinkCable`/`LinkWireless`/`LinkUniversal`, stress tests are provided to help you tweak your configuration. The [LinkUniversal_real](https://github.com/afska/gba-link-universal-test) ROM tests a more real scenario using an audio player, a background video, text and sprites.
|
||||
- The [LinkUniversal_real](https://github.com/afska/gba-link-universal-test) ROM tests a more real scenario using an audio player, a background video, text and sprites.
|
||||
- The `LinkCableMultiboot_demo` and `LinkWirelessMultiboot_demo` examples can bootstrap all other examples, allowing you to test with multiple units even if you only have one flashcart.
|
||||
|
||||
> The files use some compiler extensions, so using **GCC** is required.
|
||||
|
||||
> The example ROMs were compiled with [devkitPro](https://devkitpro.org), using GCC `14.1.0` with `-std=c++17` as the standard and `-Ofast` as the optimization level.
|
||||
> The example ROMs were compiled with [devkitARM](https://devkitpro.org), using GCC `14.1.0` with `-std=c++17` as the standard and `-Ofast` as the optimization level.
|
||||
|
||||
> To learn implementation details, you might also want to check out the [docs/](docs/) folder, which contains important documentation.
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ _(click on the emojis for documentation)_
|
|||
|
||||
Running `./compile.sh` builds all the examples with the right configuration.
|
||||
|
||||
The project must be in a path without spaces; devkitARM and some \*nix commands are required.
|
||||
The project must be in a path without spaces; _devkitARM_ and some \*nix commands are required.
|
||||
|
||||
All the projects understand these Makefile actions:
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ You can also change these compile-time constants:
|
|||
| `read(playerId)` | **u16** | Dequeues and returns the next message from player #`playerId`. If there's no data from that player, a `0` will be returned. |
|
||||
| `peek(playerId)` | **u16** | Returns the next message from player #`playerId` without dequeuing it. If there's no data from that player, a `0` will be returned. |
|
||||
| `send(data)` | - | Sends `data` to all connected players. |
|
||||
| `resetTimer()` | - | Restarts the send timer without disconnecting. |
|
||||
| `resetTimer()` | - | Restarts the send timer without disconnecting. Call this if you changed `config.interval` |
|
||||
|
||||
⚠️ `0xFFFF` and `0x0` are reserved values, so don't send them!
|
||||
|
||||
|
|
@ -182,9 +182,9 @@ You can change these compile-time constants:
|
|||
|
||||
⚠️ advanced usage only; if you're building a game, use `LinkCable`!
|
||||
|
||||
⚠️ don't send `0xFFFF`, it's a reserved value that means _disconnected client_
|
||||
⚠️ don't send `0xFFFF`, it's a reserved value that means _disconnected client_!
|
||||
|
||||
⚠️ only `transfer(...)` if `isReady()`
|
||||
⚠️ only `transfer(...)` if `isReady()`!
|
||||
|
||||
# 📻 LinkWireless
|
||||
|
||||
|
|
@ -234,7 +234,10 @@ You can also change these compile-time constants:
|
|||
- Most of these methods return a boolean, indicating if the action was successful. If not, you can call `getLastError()` to know the reason. Usually, unless it's a trivial error (like buffers being full), the connection with the adapter is reset and the game needs to start again.
|
||||
- You can check the connection state at any time with `getState()`.
|
||||
- Until a session starts, all actions are synchronous.
|
||||
- During sessions (when the state is `SERVING` or `CONNECTED`), the message transfers are IRQ-driven, so `send(...)` and `receive(...)` won't waste extra cycles. The only synchronous method that can be called during a session is `serve(...)`, which can be used to update the broadcast data.
|
||||
- During sessions (when the state is `SERVING` or `CONNECTED`), the message transfers are IRQ-driven, so `send(...)` and `receive(...)` won't waste extra cycles. Though there are some synchronous methods that can be called during a session:
|
||||
- `serve(...)`, to update the broadcast data.
|
||||
- `closeServer()`, to make it the room unavailable for new players.
|
||||
- `getSignalLevel(...)`, to retrieve signal levels.
|
||||
|
||||
| Name | Return type | Description |
|
||||
| ----------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
|
@ -259,7 +262,7 @@ You can also change these compile-time constants:
|
|||
| `playerCount()` | **u8** _(1~5)_ | Returns the number of connected players. |
|
||||
| `currentPlayerId()` | **u8** _(0~4)_ | Returns the current player ID. |
|
||||
| `getLastError([clear])` | **LinkWireless::Error** | If one of the other methods returns `false`, you can inspect this to know the cause. After this call, the last error is cleared if `clear` is `true` (default behavior). |
|
||||
| `resetTimer()` | - | Restarts the send timer without disconnecting. |
|
||||
| `resetTimer()` | - | Restarts the send timer without disconnecting. Call this if you changed `config.interval`. |
|
||||
|
||||
⚠️ `0xFFFF` is a reserved value, so don't send it!
|
||||
|
||||
|
|
@ -278,7 +281,7 @@ https://github.com/afska/gba-link-connection/assets/1631752/9a648bff-b14f-4a85-9
|
|||
| Name | Return type | Description |
|
||||
| -------------------------------------------------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `sendRom(rom, romSize, gameName, userName, gameId, players, listener, [keepConnectionAlive])` | **LinkWirelessMultiboot::Result** | Sends the `rom`. The `players` must be the exact number of consoles that will download the ROM. Once this number of players is reached, the code will start transmitting the ROM bytes. During the process, the library will continuously invoke `listener` (passing a `LinkWirelessMultiboot::MultibootProgress` object as argument), and abort the transfer if it returns `true`. The `romSize` must be a number between `448` and `262144`. It's recommended to use a ROM size that is a multiple of `16`, since this also ensures compatibility with Multiboot via Link Cable. Once completed, the return value should be `LinkWirelessMultiboot::Result::SUCCESS`. You can start the transfer before the player count is reached by running `*progress.ready = true;` in the`cancel` callback. If a `keepConnectionAlive` is `true`, the adapter won't be reset after a successful transfer, so users can continue the session using `LinkWireless::restoreExistingConnection()`. |
|
||||
| `reset` | - | Turns off the adapter. It returns a boolean indicating whether the transition to low consumption mode was successful. |
|
||||
| `reset()` | - | Turns off the adapter. It returns a boolean indicating whether the transition to low consumption mode was successful. |
|
||||
|
||||
# 🔧📻 LinkRawWireless
|
||||
|
||||
|
|
@ -316,8 +319,8 @@ https://github.com/afska/gba-link-connection/assets/1631752/9a648bff-b14f-4a85-9
|
|||
- Use `sendCommandAsync(...)` to send arbitrary commands asynchronously.
|
||||
- This requires setting `LINK_RAW_WIRELESS_ISR_SERIAL` as the `SERIAL` interrupt handler.
|
||||
- After calling this method, call `getAsyncState()` and `getAsyncCommandResult()`.
|
||||
- Note that until you retrieve the async command result, next command requests will fail!
|
||||
- When sending arbitrary commands, the responds are not parsed. The exceptions are SendData and ReceiveData, which have these helpers:
|
||||
- Do not call any other methods until the async state is `IDLE` again, or the adapter will desync!
|
||||
- When sending arbitrary commands, the responses are not parsed. The exceptions are SendData and ReceiveData, which have these helpers:
|
||||
- `getSendDataHeaderFor(...)`
|
||||
- `getReceiveDataResponse(...)`
|
||||
|
||||
|
|
@ -340,7 +343,7 @@ Additionally, there's a `LinkWirelessOpenSDK::MultiTransfer` class for file tran
|
|||
| `createClientBuffer(fullPayload, fullPayloadSize, sequence, [offset])` | **LinkWirelessOpenSDK::SendBuffer<ClientSDKHeader>** | Creates a buffer for the client to send a `fullPayload` with a valid header. If `fullPayloadSize` is higher than `14` (the maximum payload size), the buffer will only contain the **first** `14` bytes (unless an `offset` > 0 is used). A `sequence` number must be created by using `LinkWirelessOpenSDK::SequenceNumber::fromPacketId(...)`. |
|
||||
| `createClientACKBuffer(serverHeader)` | **LinkWirelessOpenSDK::SendBuffer<ServerSDKHeader>** | Creates a buffer for the client to acknowledge a header received from the host. |
|
||||
|
||||
⚠️ advanced usage only; you only need this if you want to interact with N software.
|
||||
⚠️ advanced usage only; you only need this if you want to interact with N software!
|
||||
|
||||
# 🌎 LinkUniversal
|
||||
|
||||
|
|
@ -612,7 +615,7 @@ You can also change these compile-time constants:
|
|||
| `deactivate()` | - | Deactivates the library. |
|
||||
| `report(data[3])` | - | Fills the `data` int array with a report. The first int contains _clicks_ that you can check against the bitmasks `LINK_PS2_MOUSE_LEFT_CLICK`, `LINK_PS2_MOUSE_MIDDLE_CLICK`, and `LINK_PS2_MOUSE_RIGHT_CLICK`. The second int is the _X movement_, and the third int is the _Y movement_. |
|
||||
|
||||
⚠️ calling `activate()` or `report(...)` could freeze the system if nothing is connected: detecting timeouts using interrupts is the user's responsibility.
|
||||
⚠️ calling `activate()` or `report(...)` could freeze the system if nothing is connected: detecting timeouts using interrupts is the user's responsibility!
|
||||
|
||||
## Pinout
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ _You can make this screen display any game_
|
|||
|
||||
When I started, I used the following resources to start being able to talk with the wireless adapter:
|
||||
|
||||
- [This Gist contains some details](wireless.txt)
|
||||
- [This Gist contains some details](https://gist.github.com/iracigt/50b3a857e4d82c2c11d0dd5f84ecac6b)
|
||||
- [GBATEK has a section on the wireless adapter](gbatek.md)
|
||||
|
||||
## Pinout
|
||||
|
|
@ -257,7 +257,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
#### EndHost - `0x1b`
|
||||
|
||||
- Send length: 0, response length: 2+
|
||||
- This command stops host broadcast. This allows to "close" the session and stop allowing new clients, but also **keeping the existing connections alive**. Sends and Receives still work, but:
|
||||
- This command stops host broadcast. This allows to "close" the room and stop allowing new clients, but also **keeping the existing connections alive**. Sends and Receives still work, but:
|
||||
- Clients cannot connect, even if they already know the host ID (`FinishConnection` will fail).
|
||||
- Calls to `AcceptConnections` on the host side will fail, unless `StartHost` is called again.
|
||||
|
||||
|
|
@ -286,11 +286,11 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
|
|||
|
||||
🆔 IDs are randomly generated. Each time you broadcast or connect, the adapter assigns you a new ID.
|
||||
|
||||
✅ Reading broadcasts is a three-step process: First, you send `0x1c` (you will get an ACK instantly and no data) to put the adapter in 'broadcast reading' mode, and start waiting until the adapter retrieves data (games usually wait 1 full second). Then, send a `0x1d` and it will return what's described above. Lastly, send a `0x1e` to finish the process (you can ignore what the adapter returns here) and exit broadcast reading mode. If you don't send that last `0x1e`, the next command will fail.
|
||||
✅ Reading broadcasts is a three-step process: First, you send `0x1c` (you will get an ACK instantly and no data) to put the adapter in 'broadcast reading' mode, and start waiting until the adapter retrieves data (games usually wait 1 full second). Then, send a `0x1d` and it will return what's described above. Lastly, send a `0x1e` to finish the process (you can ignore what the adapter returns here), which exits broadcast reading mode. If you don't send that last `0x1e`, the next command will fail.
|
||||
|
||||
⌚ Although games wait 1 full second, small waits (like ~160ms) also work.
|
||||
|
||||
⚙️ Calling `0x1d` repeatedly will provide an updated list of up to 4 hosts, always in the same order within each call cycle. If more than 4 hosts are available, the game must track the IDs found and loop through the `0x1c`, `0x1d`, and `0x1e` sequence to discover additional hosts. Each iteration of this sequence provides up to 4 hosts in the order they are discovered by the wireless adapter.
|
||||
⚙️ Calling `0x1d` repeatedly will provide an updated list of up to 4 hosts, always in the same order within each call. If more than 4 hosts are available, the game must track the IDs found and loop through the `0x1c`, `0x1d`, and `0x1e` sequence to discover additional hosts. Each iteration of this sequence provides up to 4 hosts in the order they are discovered by the wireless adapter.
|
||||
|
||||
⏳ If a client sends a `0x1c` and then starts a `0x1d` loop (1 command per frame), and a console that was broadcasting is turned off, it disappears after 3 seconds.
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
|
|||
|
||||
- Send length: 0, response length: 0+
|
||||
- Accepts new connections and returns a list with the connected adapters. The length of the response is zero if there are no connected adapters.
|
||||
- It includes one value per connected client, in which the most significant byte is the `clientNumber` (see [IsConnectionComplete](#isfinishedconnect---0x20)) and the least significant byte is the ID.
|
||||
- It includes one value per connected client, in which the most significant byte is the `clientNumber` (see [IsConnectionComplete](#isconnectioncomplete---0x20)) and the least significant byte is the ID.
|
||||
|
||||
🔗 If this command reports 3 connected consoles, after turning off one of them, it will still report 3 consoles. Servers need to detect timeouts in another way.
|
||||
|
||||
|
|
@ -327,7 +327,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
|
|||
[](img/wireless/0x21.png)
|
||||
|
||||
- Send length: 0, response length: 1
|
||||
- Called after [IsConnectionComplete](#isfinishedconnect---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
|
||||
- Called after [IsConnectionComplete](#isconnectioncomplete---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
|
||||
|
||||
#### SendData - `0x24`
|
||||
|
||||
|
|
@ -338,7 +338,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
|
|||
|
||||
- For hosts: the number of `bytes` that come next. For example, if we want to send `0xaabbccdd` and `0x12345678` in the same command, we need to send:
|
||||
- `0x00000008`, `0xaabbccdd`, `0x12345678`.
|
||||
- For clients: `(bytes << (3 + (1+clientNumber) * 5))`. The `clientNumber` is what I described in [IsConnectionComplete](#isfinishedconnect---0x20). For example, if we want to send a single 4-byte value (`0xaabbccdd`):
|
||||
- For clients: `(bytes << (3 + (1+clientNumber) * 5))`. The `clientNumber` is what I described in [IsConnectionComplete](#isconnectioncomplete---0x20). For example, if we want to send a single 4-byte value (`0xaabbccdd`):
|
||||
- The first client should send: `0x400`, `0xaabbccdd`
|
||||
- The second client should send: `0x8000`, `0xaabbccdd`
|
||||
- The third client should send: `0x100000`, `0xaabbccdd`
|
||||
|
|
@ -459,8 +459,8 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
|
|||
* Bits `16-23`: A 4-bit array with slots. If the console is a client, it'll have a 1 in the position assigned to that slot (e.g. the one with `clientNumber` 3 will have `0100`). The host will always have `0000` here.
|
||||
* Bits `24-31`: A number indicating the state of the adapter
|
||||
- `0` = idle
|
||||
- `1` = serving (host), closed server
|
||||
- `2` = serving (host), open server
|
||||
- `1` = serving (host), closed room
|
||||
- `2` = serving (host), open room
|
||||
- `3` = searching
|
||||
- `4` = connecting
|
||||
- `5` = connected (client)
|
||||
|
|
@ -525,7 +525,7 @@ If we analyze whether a command ID throws an 'invalid command' error (`0x996601e
|
|||
|
||||
- The extra parameter has two bitarrays:
|
||||
- Bits `0-4`: The clients that _received_ data.
|
||||
- Bits `8-11`: The clients marked as _inactive_. In theory, this depends on the # of maximum transmissions configured with the [Setup](#setup---0x17) command, but it seems to just mark them as inactive after 4 seconds.
|
||||
- Bits `8-11`: The clients marked as _inactive_. This depends on the # of maximum transmissions configured with the [Setup](#setup---0x17) command. It only marks them as inactive after 4 seconds.
|
||||
|
||||
🔗 When the adapter is disconnected from the host, it sends a `0x99660029`.
|
||||
|
||||
|
|
@ -542,7 +542,7 @@ While the clock is inverted, the acknowledge procedure is 'standard' but with th
|
|||
1. The adapter goes low as soon as it can.
|
||||
2. The GBA goes high.
|
||||
3. The adapter goes high.
|
||||
4. The GBA goes low _when it’s ready_, but **wait at least 50us**! (\*)
|
||||
4. The GBA goes low _when it’s ready_, but **wait at least 40us**! (\*)
|
||||
5. The adapter goes low when it's ready.
|
||||
6. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ void setInterval(u16 interval) {
|
|||
|
||||
void init() {
|
||||
Common::initTTE();
|
||||
Common::initTTE();
|
||||
|
||||
interrupt_init();
|
||||
|
||||
|
|
|
|||
|
|
@ -257,6 +257,7 @@ class LinkCable {
|
|||
|
||||
/**
|
||||
* @brief Restarts the send timer without disconnecting.
|
||||
* \warning Call this if you changed `config.interval`.
|
||||
*/
|
||||
void resetTimer() {
|
||||
if (!isEnabled)
|
||||
|
|
@ -357,9 +358,9 @@ class LinkCable {
|
|||
|
||||
struct Config {
|
||||
BaudRate baudRate;
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId; // can be changed in realtime, but call `resetTimer()`
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
// --------------------------------------------------------------------------
|
||||
// considerations:
|
||||
// - always set the SI terminal to an input!
|
||||
// - call reset() when you finish doing GPIO stuff!
|
||||
// - call `reset()` when you finish doing GPIO stuff!
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#ifndef LINK_DEVELOPMENT
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
// data[2] // Y movement
|
||||
// --------------------------------------------------------------------------
|
||||
// considerations:
|
||||
// - `activate()` or `report(...)` could freeze the system if not connected!
|
||||
// - detecting timeouts using interrupts is the user's responsibility!
|
||||
// - `activate()` or `report(...)` could freeze the system if not connected:
|
||||
// detecting timeouts using interrupts is the user's responsibility!
|
||||
// --------------------------------------------------------------------------
|
||||
// ____________
|
||||
// | Pinout |
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@
|
|||
// --------------------------------------------------------------------------
|
||||
// considerations:
|
||||
// - advanced usage only; if you're building a game, use `LinkCable`!
|
||||
// - don't send 0xFFFF, it's a reserved value that means <disconnected client>
|
||||
// - only transfer(...) if isReady()
|
||||
// - don't send 0xFFFF, it's a reserved value that means <disconnected client>!
|
||||
// - only `transfer(...)` if `isReady()`!
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#ifndef LINK_DEVELOPMENT
|
||||
|
|
@ -52,8 +52,6 @@ static volatile char LINK_RAW_CABLE_VERSION[] = "LinkRawCable/v8.0.0";
|
|||
|
||||
/**
|
||||
* @brief A low level handler for the Link Port (Multi-Play Mode).
|
||||
* \warning Advanced usage only!
|
||||
* \warning If you're building a game, use `LinkCable`.
|
||||
*/
|
||||
class LinkRawCable {
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@
|
|||
// interrupt handler.
|
||||
// - After calling this method, call `getAsyncState()` and
|
||||
// `getAsyncCommandResult()`.
|
||||
// - Note that until you retrieve the async command result, next command
|
||||
// requests will fail!
|
||||
// - When sending arbitrary commands, the responds are not parsed. The
|
||||
// - Do not call any other methods until the async state is `IDLE` again, or
|
||||
// the adapter will desync!
|
||||
// - When sending arbitrary commands, the responses are not parsed. The
|
||||
// exceptions are SendData and ReceiveData, which have these helpers:
|
||||
// - `getSendDataHeaderFor(...)`
|
||||
// - `getReceiveDataResponse(...)`
|
||||
|
|
@ -76,8 +76,6 @@ static volatile char LINK_RAW_WIRELESS_VERSION[] = "LinkRawWireless/v8.0.0";
|
|||
|
||||
/**
|
||||
* @brief A low level driver for the GBA Wireless Adapter.
|
||||
* \warning Advanced usage only!
|
||||
* \warning If you're building a game, use `LinkWireless`.
|
||||
*/
|
||||
class LinkRawWireless {
|
||||
private:
|
||||
|
|
@ -243,6 +241,7 @@ class LinkRawWireless {
|
|||
_LRWLOG_("setting SPI to 2Mbps");
|
||||
linkSPI.activate(LinkSPI::Mode::MASTER_2MBPS);
|
||||
|
||||
_LRWLOG_("analyzing system status");
|
||||
SystemStatusResponse systemStatus;
|
||||
if (!getSystemStatus(systemStatus)) {
|
||||
deactivate();
|
||||
|
|
@ -270,6 +269,7 @@ class LinkRawWireless {
|
|||
}
|
||||
|
||||
sessionState.currentPlayerId = systemStatus.currentPlayerId;
|
||||
_LRWLOG_("restored ok!");
|
||||
|
||||
isEnabled = true;
|
||||
return true;
|
||||
|
|
@ -547,7 +547,7 @@ class LinkRawWireless {
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Calls the BroadcastRead1 (`0x1c`) command.
|
||||
* @brief Calls the BroadcastReadStart (`0x1c`) command.
|
||||
*/
|
||||
bool broadcastReadStart() {
|
||||
bool success = sendCommand(COMMAND_BROADCAST_READ_START).success;
|
||||
|
|
@ -564,7 +564,7 @@ class LinkRawWireless {
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Calls the BroadcastRead2 (`0x1d`) command.
|
||||
* @brief Calls the BroadcastReadPoll (`0x1d`) command.
|
||||
* @param response A structure that will be filled with the response data.
|
||||
*/
|
||||
bool broadcastReadPoll(BroadcastReadPollResponse& response) {
|
||||
|
|
@ -604,7 +604,7 @@ class LinkRawWireless {
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Calls the BroadcastRead3 (`0x1e`) command.
|
||||
* @brief Calls the BroadcastReadEnd (`0x1e`) command.
|
||||
*/
|
||||
bool broadcastReadEnd() {
|
||||
bool success = sendCommand(COMMAND_BROADCAST_READ_END).success;
|
||||
|
|
|
|||
|
|
@ -367,6 +367,7 @@ class LinkUniversal {
|
|||
|
||||
/**
|
||||
* @brief Restarts the send timer without disconnecting.
|
||||
* \warning Call this if you changed `config.interval`.
|
||||
*/
|
||||
void resetTimer() {
|
||||
if (!isEnabled)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
// (see examples)
|
||||
// --------------------------------------------------------------------------
|
||||
// `send(...)` restrictions:
|
||||
// - 0xFFFF is a reserved value, so don't use it!
|
||||
// - 0xFFFF is a reserved value, so don't send it!
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#ifndef LINK_DEVELOPMENT
|
||||
|
|
@ -155,11 +155,11 @@ static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v8.0.0";
|
|||
#define LINK_WIRELESS_DEFAULT_INTERVAL 50
|
||||
#define LINK_WIRELESS_DEFAULT_SEND_TIMER_ID 3
|
||||
|
||||
#define LINK_WIRELESS_RESET_IF_NEEDED \
|
||||
if (!isEnabled) \
|
||||
return false; \
|
||||
if (linkRawWireless.getState() == LinkWireless::State::NEEDS_RESET) \
|
||||
if (!reset()) \
|
||||
#define LINK_WIRELESS_RESET_IF_NEEDED \
|
||||
if (!isEnabled) \
|
||||
return false; \
|
||||
if (linkRawWireless.getState() == State::NEEDS_RESET) \
|
||||
if (!reset()) \
|
||||
return false;
|
||||
|
||||
/**
|
||||
|
|
@ -359,8 +359,8 @@ class LinkWireless {
|
|||
const char* userName = "",
|
||||
u16 gameId = LINK_WIRELESS_MAX_GAME_ID) {
|
||||
LINK_WIRELESS_RESET_IF_NEEDED
|
||||
if (linkRawWireless.getState() != LinkWireless::State::AUTHENTICATED &&
|
||||
linkRawWireless.getState() != LinkWireless::State::SERVING)
|
||||
if (linkRawWireless.getState() != State::AUTHENTICATED &&
|
||||
linkRawWireless.getState() != State::SERVING)
|
||||
return badRequest(WRONG_STATE);
|
||||
if (Link::strlen(gameName) > LINK_WIRELESS_MAX_GAME_NAME_LENGTH)
|
||||
return badRequest(GAME_NAME_TOO_LONG);
|
||||
|
|
@ -371,14 +371,14 @@ class LinkWireless {
|
|||
if (isAsyncCommandActive())
|
||||
return badRequest(BUSY_TRY_AGAIN);
|
||||
|
||||
if (linkRawWireless.getState() != LinkWireless::State::SERVING) {
|
||||
if (linkRawWireless.getState() != State::SERVING) {
|
||||
if (!setup(config.maxPlayers))
|
||||
return abort(COMMAND_FAILED);
|
||||
}
|
||||
|
||||
bool success = linkRawWireless.broadcast(gameName, userName, gameId, false);
|
||||
|
||||
if (linkRawWireless.getState() != LinkWireless::State::SERVING)
|
||||
if (linkRawWireless.getState() != State::SERVING)
|
||||
success = success && linkRawWireless.startHost();
|
||||
|
||||
if (!success)
|
||||
|
|
@ -399,7 +399,7 @@ class LinkWireless {
|
|||
*/
|
||||
bool closeServer() {
|
||||
LINK_WIRELESS_RESET_IF_NEEDED
|
||||
if (linkRawWireless.getState() != LinkWireless::State::SERVING ||
|
||||
if (linkRawWireless.getState() != State::SERVING ||
|
||||
linkRawWireless.sessionState.isServerClosed)
|
||||
return badRequest(WRONG_STATE);
|
||||
|
||||
|
|
@ -488,7 +488,7 @@ class LinkWireless {
|
|||
*/
|
||||
bool getServersAsyncStart() {
|
||||
LINK_WIRELESS_RESET_IF_NEEDED
|
||||
if (linkRawWireless.getState() != LinkWireless::State::AUTHENTICATED)
|
||||
if (linkRawWireless.getState() != State::AUTHENTICATED)
|
||||
return badRequest(WRONG_STATE);
|
||||
|
||||
bool success = linkRawWireless.broadcastReadStart();
|
||||
|
|
@ -506,7 +506,7 @@ class LinkWireless {
|
|||
*/
|
||||
bool getServersAsyncEnd(Server servers[]) {
|
||||
LINK_WIRELESS_RESET_IF_NEEDED
|
||||
if (linkRawWireless.getState() != LinkWireless::State::SEARCHING)
|
||||
if (linkRawWireless.getState() != State::SEARCHING)
|
||||
return badRequest(WRONG_STATE);
|
||||
|
||||
LinkRawWireless::BroadcastReadPollResponse response;
|
||||
|
|
@ -545,7 +545,7 @@ class LinkWireless {
|
|||
*/
|
||||
bool connect(u16 serverId) {
|
||||
LINK_WIRELESS_RESET_IF_NEEDED
|
||||
if (linkRawWireless.getState() != LinkWireless::State::AUTHENTICATED)
|
||||
if (linkRawWireless.getState() != State::AUTHENTICATED)
|
||||
return badRequest(WRONG_STATE);
|
||||
|
||||
bool success = linkRawWireless.connect(serverId);
|
||||
|
|
@ -564,7 +564,7 @@ class LinkWireless {
|
|||
*/
|
||||
bool keepConnecting() {
|
||||
LINK_WIRELESS_RESET_IF_NEEDED
|
||||
if (linkRawWireless.getState() != LinkWireless::State::CONNECTING)
|
||||
if (linkRawWireless.getState() != State::CONNECTING)
|
||||
return badRequest(WRONG_STATE);
|
||||
|
||||
LinkRawWireless::ConnectionStatus response;
|
||||
|
|
@ -642,7 +642,7 @@ class LinkWireless {
|
|||
|
||||
/**
|
||||
* @brief Returns the current state.
|
||||
* @return One of the enum values from `LinkWireless::State`.
|
||||
* @return One of the enum values from `State`.
|
||||
*/
|
||||
[[nodiscard]] State getState() { return linkRawWireless.getState(); }
|
||||
|
||||
|
|
@ -657,8 +657,8 @@ class LinkWireless {
|
|||
* @brief Returns `true` if the state is `SERVING` or `CONNECTED`.
|
||||
*/
|
||||
[[nodiscard]] bool isSessionActive() {
|
||||
return linkRawWireless.getState() == LinkWireless::State::SERVING ||
|
||||
linkRawWireless.getState() == LinkWireless::State::CONNECTED;
|
||||
return linkRawWireless.getState() == State::SERVING ||
|
||||
linkRawWireless.getState() == State::CONNECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -697,6 +697,7 @@ class LinkWireless {
|
|||
|
||||
/**
|
||||
* @brief Restarts the send timer without disconnecting.
|
||||
* \warning Call this if you changed `config.interval`.
|
||||
*/
|
||||
void resetTimer() {
|
||||
if (!isEnabled)
|
||||
|
|
@ -898,9 +899,9 @@ class LinkWireless {
|
|||
bool forwarding;
|
||||
bool retransmission;
|
||||
u8 maxPlayers;
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId; // can be changed in realtime, but call `resetTimer()`
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -974,8 +975,8 @@ class LinkWireless {
|
|||
|
||||
#ifndef LINK_WIRELESS_TWO_PLAYERS_ONLY
|
||||
void forwardMessageIfNeeded(Message& message) {
|
||||
if (linkRawWireless.getState() == LinkWireless::State::SERVING &&
|
||||
config.forwarding && linkRawWireless.sessionState.playerCount > 2)
|
||||
if (linkRawWireless.getState() == State::SERVING && config.forwarding &&
|
||||
linkRawWireless.sessionState.playerCount > 2)
|
||||
send(message.data, message.playerId);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1017,11 +1018,11 @@ class LinkWireless {
|
|||
// SendData (end)
|
||||
|
||||
#ifdef LINK_WIRELESS_USE_SEND_RECEIVE_LATCH
|
||||
if (linkRawWireless.getState() == LinkWireless::State::CONNECTED)
|
||||
if (linkRawWireless.getState() == State::CONNECTED)
|
||||
sessionState.shouldWaitForServer = true;
|
||||
sessionState.sendReceiveLatch = !sessionState.sendReceiveLatch;
|
||||
#else
|
||||
if (linkRawWireless.getState() == LinkWireless::State::SERVING) {
|
||||
if (linkRawWireless.getState() == State::SERVING) {
|
||||
// ReceiveData (start)
|
||||
sendCommandAsync(LinkRawWireless::COMMAND_RECEIVE_DATA);
|
||||
}
|
||||
|
|
@ -1050,7 +1051,7 @@ class LinkWireless {
|
|||
addIncomingMessagesFromData(commandResult);
|
||||
|
||||
#ifndef LINK_WIRELESS_USE_SEND_RECEIVE_LATCH
|
||||
if (linkRawWireless.getState() == LinkWireless::State::CONNECTED) {
|
||||
if (linkRawWireless.getState() == State::CONNECTED) {
|
||||
// SendData (start)
|
||||
sendPendingData();
|
||||
}
|
||||
|
|
@ -1064,21 +1065,20 @@ class LinkWireless {
|
|||
}
|
||||
|
||||
LINK_INLINE void acceptConnectionsOrTransferData() { // (irq only)
|
||||
if (linkRawWireless.getState() == LinkWireless::State::SERVING &&
|
||||
if (linkRawWireless.getState() == State::SERVING &&
|
||||
!linkRawWireless.sessionState.isServerClosed &&
|
||||
!sessionState.acceptCalled &&
|
||||
linkRawWireless.sessionState.playerCount < config.maxPlayers) {
|
||||
// AcceptConnections (start)
|
||||
if (sendCommandAsync(LinkRawWireless::COMMAND_ACCEPT_CONNECTIONS))
|
||||
sessionState.acceptCalled = true;
|
||||
} else if (linkRawWireless.getState() == LinkWireless::State::CONNECTED ||
|
||||
} else if (linkRawWireless.getState() == State::CONNECTED ||
|
||||
isConnected()) {
|
||||
#ifdef LINK_WIRELESS_USE_SEND_RECEIVE_LATCH
|
||||
bool shouldReceive =
|
||||
!sessionState.sendReceiveLatch || sessionState.shouldWaitForServer;
|
||||
#else
|
||||
bool shouldReceive =
|
||||
linkRawWireless.getState() == LinkWireless::State::CONNECTED;
|
||||
bool shouldReceive = linkRawWireless.getState() == State::CONNECTED;
|
||||
#endif
|
||||
|
||||
if (shouldReceive) {
|
||||
|
|
@ -1184,7 +1184,7 @@ class LinkWireless {
|
|||
bool acceptMessage(Message& message,
|
||||
bool isConfirmation,
|
||||
u32 remotePlayerCount) { // (irq only)
|
||||
if (linkRawWireless.getState() == LinkWireless::State::SERVING) {
|
||||
if (linkRawWireless.getState() == State::SERVING) {
|
||||
u32 expectedPacketId =
|
||||
(sessionState.lastPacketIdFromClients[message.playerId] + 1) %
|
||||
MAX_PACKET_IDS;
|
||||
|
|
@ -1236,7 +1236,7 @@ class LinkWireless {
|
|||
}
|
||||
|
||||
void addConfirmations() { // (irq only)
|
||||
if (linkRawWireless.getState() == LinkWireless::State::SERVING) {
|
||||
if (linkRawWireless.getState() == State::SERVING) {
|
||||
#ifndef LINK_WIRELESS_TWO_PLAYERS_ONLY
|
||||
if (config.maxPlayers > 2 &&
|
||||
(sessionState.lastPacketIdFromClients[1] == 0 ||
|
||||
|
|
@ -1268,7 +1268,7 @@ class LinkWireless {
|
|||
bool handleConfirmation(Message confirmation) { // (irq only)
|
||||
u32 confirmationData = (confirmation.packetId << 16) | confirmation.data;
|
||||
|
||||
if (linkRawWireless.getState() == LinkWireless::State::CONNECTED) {
|
||||
if (linkRawWireless.getState() == State::CONNECTED) {
|
||||
if (confirmation.playerId == 0 &&
|
||||
!sessionState.didReceiveLastPacketIdFromServer) {
|
||||
sessionState.lastPacketIdFromServer = confirmationData;
|
||||
|
|
@ -1359,8 +1359,7 @@ class LinkWireless {
|
|||
|
||||
bool checkRemoteTimeouts() { // (irq only)
|
||||
for (u32 i = 0; i < linkRawWireless.sessionState.playerCount; i++) {
|
||||
if ((i == 0 ||
|
||||
linkRawWireless.getState() == LinkWireless::State::SERVING) &&
|
||||
if ((i == 0 || linkRawWireless.getState() == State::SERVING) &&
|
||||
sessionState.msgTimeouts[i] > config.timeout)
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1370,7 +1369,7 @@ class LinkWireless {
|
|||
#endif
|
||||
|
||||
u32 getDeviceTransferLength() { // (irq only)
|
||||
return linkRawWireless.getState() == LinkWireless::State::SERVING
|
||||
return linkRawWireless.getState() == State::SERVING
|
||||
? LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH
|
||||
: LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// An open-source implementation of the "official" Wireless Adapter protocol.
|
||||
// --------------------------------------------------------------------------
|
||||
// - advanced usage only; you only need this if you want to interact with N
|
||||
// software.
|
||||
// software!
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
#ifndef LINK_DEVELOPMENT
|
||||
|
|
@ -21,8 +21,6 @@ static volatile char VERSION[] = "LinkWirelessOpenSDK/v8.0.0";
|
|||
/**
|
||||
* @brief An open-source implementation of the "official" Wireless Adapter
|
||||
* protocol.
|
||||
* \warning Advanced usage only!
|
||||
* \warning You only need this if you want to interact with N software.
|
||||
*/
|
||||
class LinkWirelessOpenSDK {
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -288,13 +288,13 @@ class Queue {
|
|||
|
||||
void syncPush(T item) {
|
||||
_isWriting = true;
|
||||
asm volatile("" ::: "memory");
|
||||
LINK_BARRIER;
|
||||
|
||||
push(item);
|
||||
|
||||
asm volatile("" ::: "memory");
|
||||
LINK_BARRIER;
|
||||
_isWriting = false;
|
||||
asm volatile("" ::: "memory");
|
||||
LINK_BARRIER;
|
||||
|
||||
if (_needsClear) {
|
||||
clear();
|
||||
|
|
@ -304,13 +304,13 @@ class Queue {
|
|||
|
||||
T syncPop() {
|
||||
_isReading = true;
|
||||
asm volatile("" ::: "memory");
|
||||
LINK_BARRIER;
|
||||
|
||||
auto value = pop();
|
||||
|
||||
asm volatile("" ::: "memory");
|
||||
LINK_BARRIER;
|
||||
_isReading = false;
|
||||
asm volatile("" ::: "memory");
|
||||
LINK_BARRIER;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ typedef enum {
|
|||
|
||||
typedef struct {
|
||||
C_LinkCable_BaudRate baudRate;
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId; // can be changed in realtime, but call `resetTimer()`
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId;
|
||||
} C_LinkCable_Config;
|
||||
|
||||
C_LinkCableHandle C_LinkCable_createDefault();
|
||||
|
|
|
|||
|
|
@ -48,6 +48,17 @@ bool C_LinkWireless_closeServer(C_LinkWirelessHandle handle) {
|
|||
return static_cast<LinkWireless*>(handle)->closeServer();
|
||||
}
|
||||
|
||||
bool C_LinkWireless_getSignalLevel(
|
||||
C_LinkWirelessHandle handle,
|
||||
C_LinkWireless_SignalLevelResponse* response) {
|
||||
LinkWireless::SignalLevelResponse cppResponse;
|
||||
bool success =
|
||||
static_cast<LinkWireless*>(handle)->getSignalLevel(cppResponse);
|
||||
for (u32 i = 0; i < LINK_WIRELESS_MAX_PLAYERS; i++)
|
||||
response->signalLevels[i] = cppResponse.signalLevels[i];
|
||||
return success;
|
||||
}
|
||||
|
||||
bool C_LinkWireless_getServers(C_LinkWirelessHandle handle,
|
||||
C_LinkWireless_Server servers[]) {
|
||||
LinkWireless::Server cppServers[C_LINK_WIRELESS_MAX_SERVERS];
|
||||
|
|
|
|||
|
|
@ -71,11 +71,15 @@ typedef struct {
|
|||
bool forwarding;
|
||||
bool retransmission;
|
||||
u8 maxPlayers;
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId; // can be changed in realtime, but call `resetTimer()`
|
||||
u32 timeout; // can be changed in realtime
|
||||
u16 interval; // can be changed in realtime, but call `resetTimer()`
|
||||
u8 sendTimerId;
|
||||
} C_LinkWireless_Config;
|
||||
|
||||
typedef struct {
|
||||
u8 signalLevels[C_LINK_WIRELESS_MAX_PLAYERS];
|
||||
} C_LinkWireless_SignalLevelResponse;
|
||||
|
||||
C_LinkWirelessHandle C_LinkWireless_createDefault();
|
||||
C_LinkWirelessHandle C_LinkWireless_create(bool forwarding,
|
||||
bool retransmission,
|
||||
|
|
@ -96,6 +100,10 @@ bool C_LinkWireless_serve(C_LinkWirelessHandle handle,
|
|||
u16 gameId);
|
||||
bool C_LinkWireless_closeServer(C_LinkWirelessHandle handle);
|
||||
|
||||
bool C_LinkWireless_getSignalLevel(
|
||||
C_LinkWirelessHandle handle,
|
||||
C_LinkWireless_SignalLevelResponse* response);
|
||||
|
||||
bool C_LinkWireless_getServers(C_LinkWirelessHandle handle,
|
||||
C_LinkWireless_Server servers[]);
|
||||
bool C_LinkWireless_getServersAsyncStart(C_LinkWirelessHandle handle);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user