Merge pull request #17 from afska/v6.2.0

📡 LinkWirelessMultiboot, 🔧🏛️ LinkWirelessOpenSDK, 📻🌎🔌 new features
This commit is contained in:
Rodrigo Alfonso
2024-02-05 03:27:33 -03:00
committed by GitHub
38 changed files with 2949 additions and 986 deletions

197
README.md
View File

@@ -3,13 +3,15 @@
A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port. Its main purpose is to provide multiplayer support to homebrew games.
- [👾](#-LinkCable) [LinkCable.hpp](lib/LinkCable.hpp): The classic 16-bit **Multi-Play mode** (up to 4 players) using a GBA Link Cable!
- [💻](#-LinkCableMultiboot) [LinkCableMultiboot.hpp](lib/LinkCableMultiboot.hpp): ‍Send **Multiboot software** (small 256KiB ROMs) to other GBAs with no cartridge!
- [🔧👾](#-LinkRawCable) [LinkRawCable.hpp](lib/LinkRawCable.hpp): A **minimal** low-level API for the 16-bit Multi-Play mode.
- [💻](#-LinkCableMultiboot) [LinkCableMultiboot.hpp](lib/LinkCableMultiboot.hpp): ‍Send **Multiboot software** (small 256KiB ROMs) to other GBAs with no cartridge!
- [📻](#-LinkWireless) [LinkWireless.hpp](lib/LinkWireless.hpp): Connect up to 5 consoles with the **Wireless Adapter**!
- [📡](#-LinkWirelessMultiboot) [LinkWirelessMultiboot.hpp](lib/LinkWirelessMultiboot.hpp): ‍Send **Multiboot software** (small 256KiB ROMs) to other GBAs **over the air**!
- [🔧📻](#-LinkRawWireless) [LinkRawWireless.hpp](lib/LinkRawWireless.hpp): A **minimal** low-level API for the Wireless Adapter.
- [🔧🏛️](#-LinkWirelessOpenSDK) [LinkWirelessOpenSDK.hpp](lib/LinkWirelessOpenSDK.hpp): An abstraction of the **official** software level protocol of the Wireless Adapter.
- [🌎](#-LinkUniversal) [LinkUniversal.hpp](lib/LinkUniversal.hpp): Add multiplayer support to your game, both with 👾 *Link Cables* and 📻 *Wireless Adapters*, using the **same API**!
- [🔌](#-LinkGPIO) [LinkGPIO.hpp](lib/LinkGPIO.hpp): Use the Link Port however you want to control **any device** (like LEDs, rumble motors, and that kind of stuff)!
- [🔗](#-LinkSPI) [LinkSPI.hpp](lib/LinkSPI.hpp): Connect with a PC (like a **Raspberry Pi**) or another GBA (with a GBC Link Cable) using this mode. Transfer up to 2Mbit/s!
- [📻](#-LinkWireless) [LinkWireless.hpp](lib/LinkWireless.hpp): Connect up to 5 consoles with the **Wireless Adapter**!
- [🔧📻](#-LinkRawWireless) [LinkRawWireless.hpp](lib/LinkRawWireless.hpp): A **minimal** low-level API for the Wireless Adapter.
- [🌎](#-LinkUniversal) [LinkUniversal.hpp](lib/LinkUniversal.hpp): Add multiplayer support to your game, both with 👾 *Link Cables* and 📻 *Wireless Adapters*, using the **same API**.
*(click on the emojis for documentation)*
@@ -20,9 +22,11 @@ A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port.
- Include the library you want (e.g. [LinkCable.hpp](lib/LinkCable.hpp)) in your game code, and refer to its comments for instructions. Most of these libraries are provided as single header files for simplicity. The only external dependency is **libtonc**, which comes preinstalled with *devkitPro*.
- Check out the [examples](examples) folder.
* Builds are available in [Releases](https://github.com/afska/gba-link-connection/releases).
* They can be tested on real GBAs or using emulators (*mGBA*, *NO$GBA*, or *VBA-M*).
* They can be tested on real GBAs or using emulators.
* For `LinkCable`/`LinkWireless`/`LinkUniversal` there are stress tests that you can use to tweak your configuration.
To learn implementation details, you might also want to check out the [docs](docs) folder, which contains important documentation.
### Makefile actions (for all examples)
```bash
@@ -85,7 +89,7 @@ Name | Return type | Description
This tool allows sending Multiboot ROMs (small 256KiB programs that fit in EWRAM) from one GBA to up to 3 slaves, using a single cartridge.
![photo](https://user-images.githubusercontent.com/1631752/213667130-fafcbdb1-767f-4f74-98cb-d7e36c4d7e4e.jpg)
![screenshot](https://github.com/afska/gba-link-connection/assets/1631752/6ff55944-5437-436f-bcc7-a89b05dc5486)
## Methods
@@ -95,66 +99,38 @@ Name | Return type | Description
⚠️ for better results, turn on the GBAs **after** calling the `sendRom` method!
# 🔌 LinkGPIO
# 🔧👾 LinkRawCable
*(aka General Purpose Mode)*
- This is a minimal hardware wrapper designed for the *Multi-Play mode*.
- It doesn't include any of the features of [👾 LinkCable](#-LinkCable), so it's not well suited for games.
- Its demo (`LinkRawCable_demo`) can help emulator developers in enhancing accuracy.
This is the default Link Port mode, and it allows users to manipulate pins `SI`, `SO`, `SD` and `SC` directly.
![photo](https://user-images.githubusercontent.com/1631752/212867547-e0a795aa-da00-4b2c-8640-8db7ea857e19.jpg)
## Methods
Name | Return type | Description
--- | --- | ---
`reset()` | - | Resets communication mode to General Purpose. **Required to initialize the library!**
`setMode(pin, direction)` | - | Configures a `pin` to use a `direction` (input or output).
`getMode(pin)` | **LinkGPIO::Direction** | Returns the direction set at `pin`.
`readPin(pin)` | **bool** | Returns whether a `pin` is *HIGH* or not (when set as an input).
`writePin(pin, isHigh)` | - | Sets a `pin` to be high or not (when set as an output).
`setSIInterrupts(isEnabled)` | - | If it `isEnabled`, an IRQ will be generated when `SI` changes from *HIGH* to *LOW*.
⚠️ always set the `SI` terminal to an input!
# 🔗 LinkSPI
*(aka Normal Mode)*
This is the GBA's implementation of SPI. In this library, packets are set to 32-bit, as there's no benefit to using the 8-bit version. You can use this to interact with other GBAs or computers that know SPI.
![screenshot](https://user-images.githubusercontent.com/1631752/213068614-875049f6-bb01-41b6-9e30-98c73cc69b25.png)
![screenshot](https://github.com/afska/gba-link-connection/assets/1631752/29a25bf7-211e-49d6-a32a-566c72a44973)
## Methods
Name | Return type | Description
--- | --- | ---
`isActive()` | **bool** | Returns whether the library is active or not.
`activate(mode)` | - | Activates the library in a specific `mode` (one of `LinkSPI::Mode::SLAVE`, `LinkSPI::Mode::MASTER_256KBPS`, or `LinkSPI::Mode::MASTER_2MBPS`).
`activate(baudRate = BAUD_RATE_1)` | - | Activates the library in a specific `baudRate` (`LinkRawCable::BaudRate`).
`deactivate()` | - | Deactivates the library.
`transfer(data)` | **u32** | Exchanges `data` with the other end. Returns the received data.
`transfer(data, cancel)` | **u32** | Like `transfer(data)` but accepts a `cancel()` function. The library will continuously invoke it, and abort the transfer if it returns `true`.
`transferAsync(data, [cancel])` | - | Schedules a `data` transfer and returns. After this, call `getAsyncState()` and `getAsyncData()`. Note that until you retrieve the async data, normal `transfer(...)`s won't do anything!
`getAsyncState()` | **LinkSPI::AsyncState** | Returns the state of the last async transfer (one of `LinkSPI::AsyncState::IDLE`, `LinkSPI::AsyncState::WAITING`, or `LinkSPI::AsyncState::READY`).
`getAsyncData()` | **u32** | If the async state is `READY`, returns the remote data and switches the state back to `IDLE`.
`getMode()` | **LinkSPI::Mode** | Returns the current `mode`.
`setWaitModeActive(isActive)` | - | Enables or disables `waitMode` (*).
`isWaitModeActive()` | **bool** | Returns whether `waitMode` (*) is active or not.
`transfer(data)` | **LinkRawCable::Response** | Exchanges `data` with the connected consoles. Returns the received data, including the assigned player id.
`transfer(data, cancel)` | **LinkRawCable::Response** | Like `transfer(data)` but accepts a `cancel()` function. The library will continuously invoke it, and abort the transfer if it returns `true`.
`transferAsync(data)` | - | Schedules a `data` transfer and returns. After this, call `getAsyncState()` and `getAsyncData()`. Note that until you retrieve the async data, normal `transfer(...)`s won't do anything!
`getAsyncState()` | **LinkRawCable::AsyncState** | Returns the state of the last async transfer (one of `LinkRawCable::AsyncState::IDLE`, `LinkRawCable::AsyncState::WAITING`, or `LinkRawCable::AsyncState::READY`).
`getAsyncData()` | **LinkRawCable::Response** | If the async state is `READY`, returns the remote data and switches the state back to `IDLE`.
`isMaster()` | **bool** | Returns whether the console is connected as master or not. Returns garbage when the cable is not properly connected.
`isReady()` | **bool** | Returns whether all connected consoles have entered the multiplayer mode. Returns garbage when the cable is not properly connected.
`getBaudRate()` | **LinkRawCable::BaudRate** | Returns the current `baudRate`.
> (*) `waitMode`: The GBA adds an extra feature over SPI. When working as master, it can check whether the other terminal is ready to receive, and wait if it's not. That makes the connection more reliable, but it's not always supported on other hardware units (e.g. the Wireless Adapter), so it must be disabled in those cases.
>
> `waitMode` is disabled by default.
⚠️ 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!
- don't send `0xFFFF`, it's a reserved value that means *disconnected client*
- only `transfer(...)` if `isReady()`
# 📻 LinkWireless
*(aka GBA Wireless Adapter)*
This is a driver for an accessory that enables wireless games up to 5 players. The inner workings of the adapter are highly unknown, but [this article](docs/wireless_adapter.md) is very helpful. I've updated the blog post to add more details about the things I learnt by the means of ~~reverse engineering~~ brute force and trial&error.
This is a driver for an accessory that enables wireless games up to 5 players. The inner workings of the adapter are highly unknown, but [this blog post](docs/wireless_adapter.md) is very helpful. I've updated it to add more details about the things I learnt by the means of ~~reverse engineering~~ brute force and trial&error.
The library, by default, implements a lightweight protocol (on top of the adapter's message system) that sends packet IDs and checksums. This allows detecting disconnections, forwarding messages to all nodes, and retransmitting to prevent packet loss.
@@ -215,6 +191,48 @@ Name | Return type | Description
⚠️ `0xFFFF` is a reserved value, so don't send it!
# 💻 LinkWirelessMultiboot
*(aka Multiboot through Wireless Adapter)*
This tool allows sending Multiboot ROMs (small 256KiB programs that fit in EWRAM) from one GBA to up to 4 slaves, wirelessly, using a single cartridge.
https://github.com/afska/gba-link-connection/assets/1631752/9a648bff-b14f-4a85-92d4-ccf366adce2d
## Methods
Name | Return type | Description
--- | --- | ---
`sendRom(rom, romSize, gameName, userName, gameId, players, cancel)` | **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 `cancel` (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`, as this also ensures compatibility with Multiboot via Link Cable. Once completed, the return value should be `LinkWirelessMultiboot::Result::SUCCESS`.
# 🔧📻 LinkRawWireless
- This is a minimal hardware wrapper designed for the *Wireless Adapter*.
- It doesn't include any of the features of [📻 LinkWireless](#-LinkWireless), so it's not well suited for games.
- Its demo (`LinkRawWireless_demo`) can help emulator developers in enhancing accuracy.
![screenshot](https://github.com/afska/gba-link-connection/assets/1631752/bc7e5a7d-a1bd-46a5-8318-98160c1229ae)
## Methods
- There's one method for every supported wireless adapter command.
- Use `sendCommand(...)` to send arbitrary commands.
# 🔧🏛 LinkWirelessOpenSDK
All first-party games, including the Multiboot 'bootloader' sent by the adapter, use an official software-level protocol. This class provides methods for creating and reading packets that adhere to this protocol. It's supposed to be used in conjunction with [🔧📻 LinkRawWireless](#-LinkRawWireless).
## Methods
Name | Return type | Description
--- | --- | ---
`getChildrenData(response)` | **LinkWirelessOpenSDK::ChildrenData** | Parses the `response` and returns a struct containing all the received packets from the connected clients.
`getParentData(response)` | **LinkWirelessOpenSDK::ParentData** | Parses the `response` and returns a struct containing all the received packets from the host.
`createServerBuffer(fullPayload, fullPayloadSize, sequence, [targetSlots], [offset])` | **LinkWirelessOpenSDK::SendBuffer<ServerSDKHeader>** | Creates a buffer for the host to send a `fullPayload` with a valid header. If `fullPayloadSize` is higher than `84` (the maximum payload size), the buffer will only contain the **first** `84` bytes (unless an `offset` > 0 is used). A `sequence` number must be created by using `LinkWirelessOpenSDK::SequenceNumber::fromPacketId(...)`. Optionally, a `targetSlots` bit array can be used to exclude some clients from the transmissions (the default is `0b1111`).
`createServerACKBuffer(clientHeader, clientNumber)` | **LinkWirelessOpenSDK::SendBuffer<ServerSDKHeader>** | Creates a buffer for the host to acknowledge a header received from a certain `clientNumber`.
`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.
# 🌎 LinkUniversal
A multiuse library that doesn't care whether you plug a Link Cable or a Wireless Adapter. It continuously switches between both and tries to connect to other peers, supporting the hot swapping of cables and adapters and all the features from [👾 LinkCable](#-LinkCable) and [📻 LinkWireless](#-LinkWireless).
@@ -229,15 +247,16 @@ Name | Type | Default | Description
--- | --- | --- | ---
`protocol` | **LinkUniversal::Protocol** | `AUTODETECT` | Specifies what protocol should be used (one of `LinkUniversal::Protocol::AUTODETECT`, `LinkUniversal::Protocol::CABLE`, `LinkUniversal::Protocol::WIRELESS_AUTO`, `LinkUniversal::Protocol::WIRELESS_SERVER`, or `LinkUniversal::Protocol::WIRELESS_CLIENT`).
`gameName` | **const char\*** | `""` | The game name that will be broadcasted in wireless sessions (max `14` characters). The string must be a null-terminated character array. The library uses this to only connect to servers from the same game.
`cableOptions` | **LinkUniversal::CableOptions** | *same as LinkCable* | All the [👾 LinkCable](#constructor) constructor parameters in one *struct*.
`wirelessOptions` | **LinkUniversal::WirelessOptions** | *same as LinkWireless* | All the [📻 LinkWireless](#constructor-1) constructor parameters in one *struct*.
`cableOptions` | **LinkUniversal::CableOptions** | *same as LinkCable* | All the [👾 LinkCable](#-LinkCable) constructor parameters in one *struct*.
`wirelessOptions` | **LinkUniversal::WirelessOptions** | *same as LinkWireless* | All the [📻 LinkWireless](#-LinkWireless) constructor parameters in one *struct*.
You can also change these compile-time constants:
- `LINK_UNIVERSAL_MAX_PLAYERS`: to set a maximum number of players. The default value is `4` (LinkCable's limit) but can be increased to `5` to support larger wireless rooms.
- `LINK_UNIVERSAL_GAME_ID_FILTER`: to restrict wireless connections to rooms with a specific game ID (`0x0000` - `0x7fff`). The default value (`0`) connects to any game ID and uses `0x7fff` when serving.
## Methods
The interface is the same as [👾 LinkCable](#methods). Additionally, it supports these methods:
The interface is the same as [👾 LinkCable](#-LinkCable). Additionally, it supports these methods:
Name | Return type | Description
--- | --- | ---
@@ -245,45 +264,59 @@ Name | Return type | Description
`getMode()` | **LinkUniversal::Mode** | Returns the active mode (one of `LinkUniversal::Mode::LINK_CABLE`, or `LinkUniversal::Mode::LINK_WIRELESS`).
`getProtocol()` | **LinkUniversal::Protocol** | Returns the active protocol (one of `LinkUniversal::Protocol::AUTODETECT`, `LinkUniversal::Protocol::CABLE`, `LinkUniversal::Protocol::WIRELESS_AUTO`, `LinkUniversal::Protocol::WIRELESS_SERVER`, or `LinkUniversal::Protocol::WIRELESS_CLIENT`).
`setProtocol(protocol)` | - | Sets the active `protocol`.
`getWirelessState()` | **LinkWireless::State** | Returns the wireless state (same as [📻 LinkWireless](#methods-4)'s `getState()`).
`getWirelessState()` | **LinkWireless::State** | Returns the wireless state (same as [📻 LinkWireless](#-LinkWireless)'s `getState()`).
# 🔧👾 LinkRawCable
# 🔌 LinkGPIO
- This is a minimal hardware wrapper designed for the *Multi-Play mode*.
- It doesn't include any of the features of [👾 LinkCable](#-LinkCable), so it's not well suited for games.
- Its demo (`LinkRawCable_demo`) can help emulator developers in enhancing accuracy.
*(aka General Purpose Mode)*
![screenshot](https://github.com/afska/gba-link-connection/assets/1631752/29a25bf7-211e-49d6-a32a-566c72a44973)
This is the default Link Port mode, and it allows users to manipulate pins `SI`, `SO`, `SD` and `SC` directly.
![photo](https://github.com/afska/gba-link-connection/assets/1631752/b53ddc3d-46c5-441b-9036-489150d9de9f)
## Methods
Name | Return type | Description
--- | --- | ---
`reset()` | - | Resets communication mode to General Purpose. **Required to initialize the library!**
`setMode(pin, direction)` | - | Configures a `pin` to use a `direction` (input or output).
`getMode(pin)` | **LinkGPIO::Direction** | Returns the direction set at `pin`.
`readPin(pin)` | **bool** | Returns whether a `pin` is *HIGH* or not (when set as an input).
`writePin(pin, isHigh)` | - | Sets a `pin` to be high or not (when set as an output).
`setSIInterrupts(isEnabled)` | - | If it `isEnabled`, an IRQ will be generated when `SI` changes from *HIGH* to *LOW*.
⚠️ always set the `SI` terminal to an input!
# 🔗 LinkSPI
*(aka Normal Mode)*
This is the GBA's implementation of SPI. You can use this to interact with other GBAs or computers that know SPI. By default, it uses 32-bit packets, but you can switch to 8-bit by enabling the compile-time constant `LINK_SPI_8BIT_MODE`.
![screenshot](https://user-images.githubusercontent.com/1631752/213068614-875049f6-bb01-41b6-9e30-98c73cc69b25.png)
## Methods
Name | Return type | Description
--- | --- | ---
`isActive()` | **bool** | Returns whether the library is active or not.
`activate(baudRate = BAUD_RATE_1)` | - | Activates the library in a specific `baudRate` (`LinkRawCable::BaudRate`).
`activate(mode)` | - | Activates the library in a specific `mode` (one of `LinkSPI::Mode::SLAVE`, `LinkSPI::Mode::MASTER_256KBPS`, or `LinkSPI::Mode::MASTER_2MBPS`).
`deactivate()` | - | Deactivates the library.
`transfer(data)` | **LinkRawCable::Response** | Exchanges `data` with the connected consoles. Returns the received data, including the assigned player id.
`transfer(data, cancel)` | **LinkRawCable::Response** | Like `transfer(data)` but accepts a `cancel()` function. The library will continuously invoke it, and abort the transfer if it returns `true`.
`transferAsync(data)` | - | Schedules a `data` transfer and returns. After this, call `getAsyncState()` and `getAsyncData()`. Note that until you retrieve the async data, normal `transfer(...)`s won't do anything!
`getAsyncState()` | **LinkRawCable::AsyncState** | Returns the state of the last async transfer (one of `LinkRawCable::AsyncState::IDLE`, `LinkRawCable::AsyncState::WAITING`, or `LinkRawCable::AsyncState::READY`).
`getAsyncData()` | **LinkRawCable::Response** | If the async state is `READY`, returns the remote data and switches the state back to `IDLE`.
`isMaster()` | **bool** | Returns whether the console is connected as master or not. Returns garbage when the cable is not properly connected.
`isReady()` | **bool** | Returns whether all connected consoles have entered the multiplayer mode. Returns garbage when the cable is not properly connected.
`getBaudRate()` | **LinkRawCable::BaudRate** | Returns the current `baudRate`.
`transfer(data)` | **u32** | Exchanges `data` with the other end. Returns the received data.
`transfer(data, cancel)` | **u32** | Like `transfer(data)` but accepts a `cancel()` function. The library will continuously invoke it, and abort the transfer if it returns `true`.
`transferAsync(data, [cancel])` | - | Schedules a `data` transfer and returns. After this, call `getAsyncState()` and `getAsyncData()`. Note that until you retrieve the async data, normal `transfer(...)`s won't do anything!
`getAsyncState()` | **LinkSPI::AsyncState** | Returns the state of the last async transfer (one of `LinkSPI::AsyncState::IDLE`, `LinkSPI::AsyncState::WAITING`, or `LinkSPI::AsyncState::READY`).
`getAsyncData()` | **u32** | If the async state is `READY`, returns the remote data and switches the state back to `IDLE`.
`getMode()` | **LinkSPI::Mode** | Returns the current `mode`.
`setWaitModeActive(isActive)` | - | Enables or disables `waitMode` (*).
`isWaitModeActive()` | **bool** | Returns whether `waitMode` (*) is active or not.
- don't send `0xFFFF`, it's a reserved value that means *disconnected client*
- only `transfer(...)` if `isReady()`
> (*) `waitMode`: The GBA adds an extra feature over SPI. When working as master, it can check whether the other terminal is ready to receive, and wait if it's not. That makes the connection more reliable, but it's not always supported on other hardware units (e.g. the Wireless Adapter), so it must be disabled in those cases.
>
> `waitMode` is disabled by default.
# 🔧📻 LinkRawWireless
⚠️ when using Normal Mode between two GBAs, use a GBC Link Cable!
- This is a minimal hardware wrapper designed for the *Wireless Adapter*.
- It doesn't include any of the features of [📻 LinkWireless](#-LinkWireless), so it's not well suited for games.
- Its demo (`LinkRawWireless_demo`) can help emulator developers in enhancing accuracy.
⚠️ only use the 2Mbps mode with custom hardware (very short wires)!
![screenshot](https://github.com/afska/gba-link-connection/assets/1631752/bc7e5a7d-a1bd-46a5-8318-98160c1229ae)
## Methods
- There's one method for every supported wireless adapter command.
- Use `sendCommand(...)` to send arbitrary commands.
⚠️ don't send `0xFFFFFFFF`, it's reserved for errors!

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,509 +0,0 @@
Nintendo Wireless Adapter Specification
v1.1 (23th June 2005)
by denopqrihg (denopqrihg@centrum.cz)
This document describes how a GBA program can communicate with a plugged-in Nintendo wireless adapter. This info is not 100% accurate, nor am I responsible for any damage it may do. I am only sharing my partial knowledge.
Some general info
-----------------
The Nintendo Wireless Adapter is a device that presents more multiplayer features than the link cable. It uses a kind of server/client approach (broadcasting, connections etc.). It allows to send quite large amounts of data at once (most I've seen was 80 bytes), has built-in synchronisation and much more.
This document is only about two player link. At the moment, I have no info about how it works with mre players.
If you don't understand something in this document or think something is unclear, contact me and I'll try to clarify.
Which mode to use?
------------------
32-bit Normal mode, all the time. No exceptions.
Mostly 2Mbit internal clock, but there are exceptions which will be pointed out.
Initialization
--------------
Use: 32-bit Normal mode, 256Khz internal clock.
Send the text "NINTENDO/0x01/0x80" in the following manner:
Store two bytes of the text in the lower 16 bits of the send data. The upper 16 bits should be 1's complement of the previously received lower 16 bits. Send it. Received data should look like the one you sent, except that the upper and lower halfword are swapped. Repeat this process until you have sent the whole string (the last two bytes are 0x8001).
Example:
Send Receive
0xgggg494e 0x494egggg
0xb6b1494e 0x494eb6b1
0xb6b1544e 0x544eb6b1
0xabb1544e 0x544eabb1
0xabb14e45 0x4e45abb1
0xb1ba4e45 0x4e45b1ba
0xb1ba4f44 0x4f44b1ba
0xb0bb4f44 0x4f44b0bb
0xb0bb8001 0x8001b0bb
(gggg = garbage)
Then send command 0x3d or 0x10, difference is unknown (what is a command and how to use it is in the next section).
Communication
-------------
The adapter uses a form of communication that I call 'commands'.
It waits until you send a command to it, and then it does something according to what command you sent.
The format is:
0x9966wwcc
cc is the 1-byte command
ww is a number indicating how many 4-byte units of data will follow the command (I don't know what happens when you send data with a command that shouldn't send any).
When you send this, you always receive 0x80000000 (also when sending the additional data).
Then, you must send 0x80000000. You then receive a similar structure, 0x9966wwaa.
ww is again a number of words (4 byte) following this command
aa is the command you sent ORed with 0x80 (with some exceptions)
If there was a nonzero word count in the response, send 0x80000000 the apropriate number of times and store the data you receive (you requested it, so you should store it).
Remember to keep some delays between transfers.
Example:
Send Receive
0x99660117 0x80000000
0x003c0720 0x80000000
0x80000000 0x99660097
Another example:
Send Receive
0x9966001a 0x80000000
0x80000000 0x9966019a
0x80000000 0x00001547
Ackowledging
------------
After each transfer (except initialization), a kind of 'acknowledge procedure' must be done (maybe it isn't necessary, but the games do it):
Right after the transfer ends, output SO=low, wait until SI becomes high, then output SO=high and wait until SI=low.
!! Exception !! After commands 0xa5 and 0xa7, the polarity is 'swapped': output SO=low, wait until SI=low, then send SO=high and wait until SI=high
Also, just before each transfer, set SO=low
(This part is a bit unclear to me, maybe it doesn't work like this at all)
Commands
-----------------
Many commands have completely unknown function, some of them must be sent but nobody knows why and what they mean.
This is not a complete list of commands, I only listed the commands that either are important or I know something about them.
I will use the following notation:
Command number
--------------
S: how many words to send
R: how many words to receive
Description of what the command does (if known :-)
0x10
----
S: 0
R: 0
Last step of initialization, difference between this and 0x3d is unknown, both are used
0x10 seems to be 'safer'
0x11
----
S: 0
R: 1
This command always receives 0xff in 2 player link, maybe it has to do with 3+ players ??
0x13
----
S: 0
R: 1
Receives a random value, don't know what it's for
0x16
----
S: usually 6
R: 0
Sends 'broadcast' data, which will be received by all adapters in range
0x17
----
S: 1
R: 0
Setup?? The value sent should be 003d0620 (Digimon), 003c0420 (Pokemon) or something along this way.
0x1a
----
S: 0
R: 0 or 1
This command is used by the 'server' to check if someone wants to connect. If not, no data is received. If yes, you'll get a value (random? or ID?)
0x1d
0x1e
----
S: 0
R: multiples of 7 (usually)
Read 'broadcast' data. Data from each adapter is prefixed by one word containing an 'ID' of the adapter , which is used when you want to connect.
0x1f
----
S: 1
R: 0
Connect to server. Value sent is the 'ID' of the adapter you want to connect to.
0x20
----
S: 0
R: 1
Used after 0x1f, generally resend until highest byte is 0.
0x21
----
S: 0
R: 1
Send once after 0x20 successfully finished (returned 0).
0x24
----
S: anything
R: 0
Send data to connected adapters (I've only seen this command used by servers, but maybe clients can use it as well)
0x25
----
S: anything
R: 0
Same as 0x24, with the exception that this command 'waits' until incoming data is available.
This keeps the games synchronized without any further in-game programming.
How to use: After you send all the data and get 0x996600a5 from the adapter, store 0x80000000 in the send data register and start a transfer with _EXTERNAL_ clock. Once the data arrives, you'll receive a 0x99660028 command (which you have to ackowledge just like the adapter - send back 0x996600a8).
Also note that after you receive the 0x996600a5 until you use the next command (usually 0x26), the polarity of the SI and SO conmfirmation procedure is swapped.
0x26
----
S: 0
R: anything
Receive data.
0x27
----
S: 0
R: 0
Similar to 0x25, but without sending data. All strange behavior of 0x25 applies here, too.
0x30
----
S: 1?
R: 0
Most likely a 'Terminate connection' command. Data value is usually 1.
0x3d
----
S: 0
R: 0
Same as 0x10
0xa8
----
S: 0
R: 0
Ackowledge of 0x28 sent by adapter.
0xee
----
S: 1?
R: 0
This is very unconfirmed, but is probably used when you want the adapter to resend the last command/whatever ??
Example
-------------
A complete example of a client/server communication
(all numbers are in hex)
Server
------
value of
0x128 reg Send Receive
5084 00000000 garbage
5085 ffff494e 494egggg
5085 b6b1494e 494eb6b1
5085 b6b1544e 544eb6b1
5085 abb1544e abb1544e
5085 abb14e45 4e45abb1
5085 b1ba4e45 4e45b1ba
5085 b1ba4f44 4f44b1ba
5085 b0bb4f44 4f44b0bb
5085 b0bb8001 8001b0bb
5003
5083 99660010 80000000
500b
5003
5083 80000000 99660090
500b
5003
5083 99660117 80000000
500b
5003
5083 003c0420 80000000
500b
5003
5083 80000000 99660097
500b
5003
5003
5083 99660616 80000000
500b
5003
5083 0c020002 80000000
500b
5003
5083 00005ce1 80000000
500b
5003
5083 00000000 80000000
500b
5003
5083 09000040 80000000
500b
5003
5083 c1cfc8cd 80000000
500b
5003
5083 00ffccbb 80000000
500b
5003
5083 80000000 99660096
500b
5003
5083 99660013 80000000
500b
5003
5083 80000000 99660193
500b
5003
5083 80000000 02001234 (example)
500b
5003
5083 99660019 80000000
500b
5003
5083 80000000 99660099
500b
|------------------------------------------------
v |
5003 |
5083 9966001a 80000000 |
500b |
5003 |
5083 80000000 9966009a |
500b |
repeat ------------------------------------------
until you get
|----------------------------------------------------------------
v |
5003 |
5083 9966001a 80000000 |
500b |
5003 |
5083 80000000 9966019a |
5003 |
5083 80000000 00004875 (example) |
500b |
5003 |
5083 99660011 80000000 |
500b |
5003 |
5083 80000000 99660191 |
500b |
5003 |
5083 80000000 000000ff |
500b |
repeat a few times to ensure that it really is a connection -----
5003
5083 99660124 80000000
500b
5003
5083 12345678 80000000
500b
5003
5083 80000000 99660094
500b
|------------------------------------------------
v |
5003 |
5083 99660026 80000000 |
500b |
5003 80000000 996600a6 |
500b |
repeat until you get some data -----------------
send & receive etc. etc.
Most games switch to 0x25 for the server as well after a while.
when you want to exit
5003
5083 99660130 80000000
500b
5003
5083 00000001 80000000
500b
5003
5083 80000000 996600b0
500b
Client
------
value of
0x128 reg Send Receive
5084 00000000 garbage
5085 ffff494e 494egggg
5085 b6b1494e 494eb6b1
5085 b6b1544e 544eb6b1
5085 abb1544e abb1544e
5085 abb14e45 4e45abb1
5085 b1ba4e45 4e45b1ba
5085 b1ba4f44 4f44b1ba
5085 b0bb4f44 4f44b0bb
5085 b0bb8001 8001b0bb
5003
5083 99660010 80000000
500b
5003
5083 80000000 99660090
500b
5003
5083 99660117 80000000
500b
5003
5083 003c0420 80000000
500b
5003
5083 80000000 99660097
500b
5003
5003
5083 99660616 80000000
500b
5003
5083 0c020002 80000000
500b
5003
5083 00005ce1 80000000
500b
5003
5083 00000000 80000000
500b
5003
5083 09000040 80000000
500b
5003
5083 c1cfc8cd 80000000
500b
5003
5083 00ffccbb 80000000
500b
5003
5083 80000000 99660096
500b
|------------------------------------------------------------------------
v |
5003 |
5083 99660017 80000000 |
500b |
5003 |
5083 80000000 99660797 |
500b |
5003 |
5083 80000000 00002154 adapter's ID |
500b |
5003 |
5083 80000000 0c020002 |
500b |
5003 |
5083 80000000 00005ce1 |
500b |
5003 |
5083 80000000 00000000 |
500b |
5003 |
5083 80000000 09000040 |
500b |
5003 |
5083 80000000 c1cfc8cd |
500b |
5003 |
5083 80000000 00ffccbb |
500b |
repeat, display available servers and wait until the player picks one ---
5003
5083 9966011f 80000000
500b
5003
5083 00002154 80000000 pass the ID of the appropriate adapter
500b
5003
5083 80000000 9966009f
500b
|------------------------------------------------
v |
5003 |
5083 99660020 80000000 |
500b |
5003 |
5083 80000000 996601a0 |
500b |
5003 |
5083 80000000 00001567 |
500b if highest byte wasn't 0, repeat --------
5003
5083 99660021 80000000
500b
5003
5083 80000000 996600a1
500b
5003
5083 99660027 80000000
500b
5003
5083 80000000 996600a7 now you wait until SI = SO, not the other way around
500b
5002
5082 80000000 99660028 !! EXTERNAL clock
500a
5002
5082 996600a8 80000000
500a
5003
5083 99660026 80000000 up till now
500b
5003
5083 80000000 996601a6
500b
5003
5083 80000000 12345678
500b
5003
5083 996602a5 80000000
500b
5003
5083 87654321 80000000
500b
5003
5083 45678123 80000000
500b
5003
5083 80000000 996600a5 here again, SI = SO
500b
5002
5082 80000000 99660028 !! EXTERNAL clock
500a
5002
5082 996600a8 80000000
500a
5003
5083 99660026 80000000 from here it's normal
etc. etc.
when you want to exit
5003
5083 99660130 80000000
500b
5003
5083 00000001 80000000
500b
5003
5083 80000000 996600b0
500b
Thanks to
---------
Lizardon for trying out my 'pimped' ROMS on his adapter
Nintendo for not giving away ANY info on this thing :-( (at least it was nice practice to figure it out)
You for reading the whole example

View File

@@ -4,6 +4,8 @@ Game Boy Advance Wireless Adapter
- 🌎 **Original post**: https://blog.kuiper.dev/gba-wireless-adapter 🌎
- ✏️ **Updates**: [@davidgfnet](https://github.com/davidgfnet) and I were discovering new things and we added them here!
> You can learn more details by reading [LinkRawWireless.hpp](../lib/LinkRawWireless.hpp)'s code.
The Wireless Adapter
====================
@@ -203,14 +205,6 @@ Commands are how you tell the adapter to do things. When in command mode the clo
⌛ If this acknowledge procedure doesn't complete, the adapter "gives up" after ~800μs and start listening again for commands. That means that if a game doesn't implement this logic, it has to wait almost 1 millisecond between transfers (vs ~40μs in normal scenarios).
🔀 Also, the ACK protocol is different after a [Wait](#waiting) command:
1. The GBA goes high as soon as it can.
2. The adapter goes high.
3. The GBA goes low _when it’s ready_.
4. The adapter goes low when it’s ready.
5. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
Whenever either side expects something to be sent from the other (as SPI is always dual direction, although one side is often not used), the value `0x80000000` is used.
### List of commands
@@ -241,6 +235,10 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
⚠️ Clients must always set `maxPlayers` to `00`.
🛰️ Bits `8-15` specify the number of times the adapter would perform a transmission. The default is `0`, which means infinite retransmissions. Setting a value of `3` means: transmit once, and only retry two times if the other console didn't receive data. After the maximum number of transmissions is reached, the client is marked as _inactive_ and will appear on the extra parameter that the adapter sends (`0x99660128`) in the [waiting commands](#waiting).
⏲️ Bits `0-7` represent the timeout of the [waiting commands](#waiting)(#waiting). The default is _no timeout_ (`0`), but if this is set, the adapter will issue a `0x99660027` command after the timeout is reached. It's expressed in _frames_ (units of 16.6 ms).
#### Broadcast - `0x16`
[![Image without alt text or caption](img/0x16.png)](img/0x16.png)
@@ -255,7 +253,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
(if you read from right to left, it says `ICE CLIMBER` - `NINTENDO`)
🆔 The **Game ID** is what games use to avoid listing servers from another game. This is done on the software layer (GBA), the adapter does not enforce this in any way, nor does gba-link-connection.
🆔 The **Game ID** is what games use to avoid listing servers from another game. This is done on the software layer (GBA), the adapter does not enforce this in any way, nor does gba-link-connection (unless `LINK_UNIVERSAL_GAME_ID_FILTER` is set).
🔥 This command can be called to update the broadcast data even when the server has already started using `StartHost`. Some games include metadata in the game/user name fields, such as the player's gender or a busy flag.
@@ -382,7 +380,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
- **Host**: `ReceiveData`
- Receives `{rcvHeader}`, 20
🔁 This command can also be used with one header and **no data**. In this case, it will resend the last N bytes (based on the header) of the last packet.
🔁 This command can also be used with one header and **no data**. In this case, it will resend the last N bytes (based on the header) of the last packet. Until we have a better name, we'll call this **ghost sends**.
#### SendDataWait - `0x25`
@@ -523,15 +521,181 @@ Waiting
* The GBA then sends the response back (e.g. `0x996600A8` as `0x28` + `0x80` = `0xA8`).
* After this, control of the clock returns to the GBA, and it can start sending commands back again. For example this might be receiving the command sent by the other device using [ReceiveData](#receivedata---0x26).
⌚ This timeouts after 500ms of the adapter not having anything to tell the GBA about. In this case, the adapter sends `0x99660027`. **This is only true if the console has used the Setup command before**. The value that most games use (`0x003C0420`) seems to contain this timeout value, but the default is zero (no timeout).
⌚ This timeouts after 500ms of the adapter not having anything to tell the GBA about. In this case, the adapter sends `0x99660027`. **This is only true if the console has used the [Setup](#setup---0x17) command before**. The value that most games use (`0x003C0420`) contains this timeout value, but the default is zero (no timeout).
✅ When there's new data available, the adapter sends to the GBA a `0x99660028`.
🔗 When the adapter is disconnected from the parent, it sends a `0x99660029`.
💨 Clients receive the `0x28` when new data from the host is available, but the host receives it immediately (well, after the transfer completes), as it can be used to know which clients received data or are disconnected.
⚠️ If some children didn't receive the data, the adapter sends to the host GBA a `0x99660128`.
- The extra parameter has two bitarrays:
* Bits `0-4`: The clients that _received_ data.
* Bits `8-11`: The clients marked as _inactive_. This depends on the # of maximum transmissions configured with the [Setup](#setup---0x17) command.
🔗 When the adapter is disconnected from the host, it sends a `0x99660029`.
- Bit 8 of the response indicates the reason:
* `0` = manual disconnect (aka the host used [DisconnectClient](#disconnectclient---0x30))
* `1` = the connection was lost
◀ **Inverted ACKs**
The ACK protocol changes while the clock is inverted.
Right after the adapter responds to a `0x9966xx25` with `0x996600A5`, it behaves like this:
1. The adapter stays high until the GBA goes high
2. The adapter goes low
3. The GBA goes low
[![Image without alt text or caption](img/clock-inversion-ack-start.png)](img/clock-inversion-ack-start.png)
Then, when the adapter issues commands to the GBA, the acknowledge procedure is 'standard', but with the inverted roles:
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_.
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.
Wireless Multiboot
------------------
> You can learn more details by reading [LinkWirelessMultiboot.hpp](../lib/LinkWirelessMultiboot.hpp)'s code.
To host a 'multiboot' room, a host sets the **multiboot flag** (bit 15) in its game ID (inside broadcast data) and starts serving.
- 1) For each new client that connects, it runs a small handshake where the client sends their 'game name' and 'player name'. The bootloader always sends `RFU-MB-DL` as game name and `PLAYER A` (or `B`, `C`, `D`) as player name.
- 2) When the host player confirms that all players are ready, it sends a 'rom start' command.
- 3) The host sends the rom bytes in 84-byte chunks.
- 4) The host sends a 'rom end' command and the games boot.
### Valid header
The bootloader will only accept ROMs with valid headers: they must contain this in its bytes `4-15`:
`0x52, 0x46, 0x55, 0x2d, 0x4d, 0x42, 0x4f, 0x4f, 0x54, 0x00, 0x00, 0x00`
(this represents the string `RFU-MB-DL` and zeros)
### Custom protocol
> You can learn more details by reading [LinkWirelessOpenSDK.hpp](../lib/LinkWirelessOpenSDK.hpp)'s code.
All this communication uses a custom software-layer protocol made by Nintendo, the same one used by first-party games.
Server buffers use a 3-byte header:
```c++
struct ServerSDKHeader {
unsigned int payloadSize : 7;
unsigned int _unused_ : 2;
unsigned int phase : 2;
unsigned int n : 2;
unsigned int isACK : 1;
CommState commState : 4;
unsigned int targetSlots : 4;
}
```
Clients use a 2-byte header:
```c++
struct ClientSDKHeader {
unsigned int payloadSize : 5;
unsigned int phase : 2;
unsigned int n : 2;
unsigned int isACK : 1;
CommState commState : 4;
}
```
...and `CommState` is:
```c++
enum CommState : unsigned int {
OFF = 0,
STARTING = 1,
COMMUNICATING = 2,
ENDING = 3,
DIRECT = 4
};
```
- All transfers have sequence numbers (`n` and `phase`) unless `commState` is `DIRECT` (a sort of UDP).
- There's a short initialization ritual until reaching the `COMMUNICATING` state.
- Once the `COMMUNICATING` state is reached, the initial sequence is `n=1, phase=0`.
- After each packet, the other node responds with a packet containing the same `n`, `phase` and `commState`, but with the `isACK` bit set.
- The sequence continues: `n=1,ph=1` | `n=1,ph=2` | `n=1,ph=3` | `n=2,ph=0` | `n=2,ph=1` | `n=2,ph=2` | `n=2,ph=3` | `n=3,ph=0` | `n=3,ph=1` | `n=3,ph=2` | `n=3,ph=3` | `n=0,ph=0` | `n=0,ph=1` | `n=0,ph=2` | `n=0,ph=3` | `n=1,ph=0` | `n=0,ph=1` | etc.
- Repeated or old sequence numbers are ignored, that's how they handle retransmission.
- Transfers can contain more than one packet.
- As the maximum transfer lengths are `87` (server) and `16` (client), based on header sizes, the maximum payload lengths are `84` and `14`.
- The `targetSlots` field inside the server header is a bit array that indicates which clients the message is directed to. E.g. `0b0100` means 'client 2 only' and `0b1111` means 'all clients'.
### (1) Client handshake
- Server: repeatedly performs _ghost sends_ (see [SendData](#senddata---0x24)) until the client talks
- Client: sends `0x06010486`, `0x00001A00`
- Header: `0x0486` (`size=6, n=1, ph=0, ack=0, commState=1`) (`1 = STARTING`)
- Payload: `0x01`, `0x06`, `0x00`, `0x1A`, `0x00`, `0x00`
- Server: ACKs the packet (`size=0, n=1, ph=0, ack=1, commState=1`)
- Client: sends `0x00000501`
- Header: `0x0501` (`size=1, n=2, ph=0, ack=0, commState=1`)
- Payload: `0x00`
- Server: ACKs the packet
- Client: sends `0x00000886`, `0x2D554652`
- Header: `0x0886` (`size=6, n=1, ph=0, ack=0, commState=2`) (`2 = COMMUNICATING`)
- Payload: `0x00`, `0x00`, `0x52`, `0x46`, `0x55`, `0x2D`
- => `RFU-`
- Server: ACKs the packet
- Client: sends `0x424D08A6`, `0x004C442D`
- Header: `0x08A6` (`size=6, n=1, ph=1, ack=0, commState=2`)
- Payload: `MB-DL`
- Server: ACKs the packet
- Client: sends `0x000008C6`, `0x50000000`
- Header: `0x08C6` (`size=6, n=1, ph=2, ack=0, commState=2`)
- Payload: `P`
- Server: ACKs the packet
- Client: sends `0x414C08E6`, `0x20524559`
- Header: `0x08E6` (`size=6, n=1, ph=3, ack=0, commState=2`)
- Payload: `LAYER`
- Server: ACKs the packet
- Client: sends `0x00410902`
- Header: `0x0902` (`size=2, n=2, ph=0, ack=0, commState=2`)
- Payload: `A`
- Server: ACKs the packet
- Client: sends `0x00000C00`
- Header: `0x0C00` (`size=0, n=0, ph=0, ack=0, commState=3`) (`3 = ENDING`)
- No payload
- Server: ACKs the packet
- Client: sends `0x00000080`
- Header: `0x0080` (`size=0, n=1, ph=0, ack=0, commState=0`) (`0 = OFF`)
- No payload
## (2) ROM start command
- Server: sends `0x00044807`, `0x00000054`, `0x00000002`
- Header: `0x044807` (`size=7, n=1, ph=0, ack=0, commState=1`) (`1 = STARTING`)
- Payload: `0x00`, `0x54`, `0x00`, `0x00`, `0x00`, `0x02`, `0x00`
- Client: ACKs the packet (`size=0, n=1, ph=0, ack=1, commState=1`)
## (3) ROM bytes
- At this stage, `commState` is already `2` (`COMMUNICATING`) and the ROM bytes are sent in 84-byte chunks.
- The last transfer is also `84` bytes, no matter the ROM size (it's padded with zeros).
- A number (2~4) of 'inflight packets' is allowed to speed up transfers.
- Packets without an ACK are retransmitted.
## (4) ROM end command
After all ROM chunks are ACK'd, the last transfers are:
- `size=0, n=0, ph=0, ack=0, commState=3` (`3 = ENDING`)
- `size=0, n=1, ph=0, ack=0, commState=0` (`0 = OFF`)
SPI config
----------

View File

@@ -31,7 +31,7 @@ int main() {
// Hardcoded ROM length
// This is optional, you could also use `LINK_CABLE_MULTIBOOT_MAX_ROM_SIZE`
// (but the transfer will be painfully slow)
u32 romSize = 64880;
u32 romSize = 39264;
// Note that this project's Makefile pads the ROM to a 0x10 boundary
// (as required for Multiboot).
@@ -49,7 +49,8 @@ int main() {
// Sender options
if (isSenderMode) {
if (result != LinkCableMultiboot::Result::SUCCESS)
log("Press START to send the ROM...\nPress B to set client mode...");
log("LinkCableMultiboot_demo\n (v6.2.0)\n\nPress START to send the "
"ROM...\nPress B to set client mode...");
if (keys & KEY_START) {
log("Sending... (SELECT to cancel)");
@@ -57,7 +58,7 @@ int main() {
// (3) Send the ROM
result =
linkCableMultiboot->sendRom((const void*)MEM_EWRAM, romSize, []() {
linkCableMultiboot->sendRom((const u8*)MEM_EWRAM, romSize, []() {
u16 keys = ~REG_KEYS & KEY_ANY;
return keys & KEY_SELECT;
});

View File

@@ -45,7 +45,7 @@ int main() {
u16 keys = ~REG_KEYS & KEY_ANY;
linkCable->send(keys + 1); // (avoid using 0)
std::string output = "";
std::string output = "LinkCable_basic (v6.2.0)\n\n";
if (linkCable->isConnected()) {
u8 playerCount = linkCable->playerCount();
u8 currentPlayerId = linkCable->currentPlayerId();

View File

@@ -14,11 +14,11 @@ static std::unique_ptr<TestScene> testScene{new TestScene(engine)};
#ifndef USE_LINK_UNIVERSAL
LinkCable* linkCable = new LinkCable();
LinkCable* link = linkCable;
LinkCable* linkConnection = linkCable;
#endif
#ifdef USE_LINK_UNIVERSAL
LinkUniversal* linkUniversal = new LinkUniversal();
LinkUniversal* link = linkUniversal;
LinkUniversal* linkConnection = linkUniversal;
#endif
int main() {
@@ -32,19 +32,19 @@ int main() {
u16 keys = ~REG_KEYS & KEY_ANY;
// enable and disable
if ((keys & KEY_DOWN) && link->isActive()) {
link->deactivate();
if ((keys & KEY_DOWN) && linkConnection->isActive()) {
linkConnection->deactivate();
DEBULOG("! stopped");
}
if ((keys & KEY_START) && !link->isActive()) {
link->activate();
if ((keys & KEY_START) && !linkConnection->isActive()) {
linkConnection->activate();
DEBULOG("! started");
}
// log player id/count and important flags
TextStream::instance().setText(
"P" + asStr(link->currentPlayerId()) + "/" +
asStr(link->playerCount()) + "-R" +
"P" + asStr(linkConnection->currentPlayerId()) + "/" +
asStr(linkConnection->playerCount()) + "-R" +
asStr(isBitHigh(REG_SIOCNT, LINK_CABLE_BIT_READY)) + "-S" +
asStr(isBitHigh(REG_SIOCNT, LINK_CABLE_BIT_START)) + "-E" +
asStr(isBitHigh(REG_SIOCNT, LINK_CABLE_BIT_ERROR)),
@@ -93,10 +93,10 @@ inline void setUpInterrupts() {
void printTutorial() {
#ifndef USE_LINK_UNIVERSAL
DEBULOG("LinkCable demo");
DEBULOG("LinkCable_full (v6.2.0)");
#endif
#ifdef USE_LINK_UNIVERSAL
DEBULOG("LinkUniversal demo");
DEBULOG("LinkUniversal_full (v6.2.0)");
#endif
DEBULOG("");

View File

@@ -8,10 +8,10 @@
// #define USE_LINK_UNIVERSAL
#ifndef USE_LINK_UNIVERSAL
extern LinkCable* link;
extern LinkCable* linkConnection;
#endif
#ifdef USE_LINK_UNIVERSAL
extern LinkUniversal* link;
extern LinkUniversal* linkConnection;
#endif
#endif // MAIN_H

View File

@@ -21,7 +21,7 @@ static std::unique_ptr<InputHandler> selectHandler =
inline void send(u16 data) {
DEBULOG("-> " + asStr(data));
link->send(data);
linkConnection->send(data);
}
std::vector<Background*> TestScene::backgrounds() {
@@ -43,7 +43,7 @@ void TestScene::tick(u16 keys) {
return;
// sync
link->sync();
linkConnection->sync();
frameCounter++;
@@ -55,12 +55,13 @@ void TestScene::tick(u16 keys) {
selectHandler->setIsPressed(keys & KEY_SELECT);
// log events
if (!isConnected && link->isConnected()) {
if (!isConnected && linkConnection->isConnected()) {
isConnected = true;
initialized = false;
DEBULOG("! connected (" + asStr(link->playerCount()) + " players)");
DEBULOG("! connected (" + asStr(linkConnection->playerCount()) +
" players)");
}
if (isConnected && !link->isConnected()) {
if (isConnected && !linkConnection->isConnected()) {
isConnected = false;
DEBULOG("! disconnected");
}
@@ -71,7 +72,8 @@ void TestScene::tick(u16 keys) {
// determine which value should be sent
u16 value = LINK_CABLE_NO_DATA;
if (!initialized && link->isConnected() && link->currentPlayerId() == 1) {
if (!initialized && linkConnection->isConnected() &&
linkConnection->currentPlayerId() == 1) {
initialized = true;
value = 999;
}
@@ -91,11 +93,11 @@ void TestScene::tick(u16 keys) {
}
// process received data
if (link->isConnected()) {
for (u32 i = 0; i < link->playerCount(); i++) {
while (link->canRead(i)) {
u16 message = link->read(i);
if (i != link->currentPlayerId())
if (linkConnection->isConnected()) {
for (u32 i = 0; i < linkConnection->playerCount(); i++) {
while (linkConnection->canRead(i)) {
u16 message = linkConnection->read(i);
if (i != linkConnection->currentPlayerId())
DEBULOG("<-p" + asStr(i) + ": " + asStr(message) + " (frame " +
asStr(frameCounter) + ")");
}

View File

@@ -33,7 +33,7 @@ u32 toMs(u32 cycles);
#ifndef USE_LINK_UNIVERSAL
LinkCable* linkCable = new LinkCable();
LinkCable* link = linkCable;
LinkCable* linkConnection = linkCable;
#endif
#ifdef USE_LINK_UNIVERSAL
LinkUniversal* linkUniversal =
@@ -53,7 +53,7 @@ LinkUniversal* linkUniversal =
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
.asyncACKTimerId = 0});
LinkUniversal* link = linkUniversal;
LinkUniversal* linkConnection = linkUniversal;
#endif
void init() {
@@ -89,13 +89,13 @@ int main() {
while (true) {
#ifndef USE_LINK_UNIVERSAL
std::string output = "LinkCable\n\n";
std::string output = "LinkCable_stress (v6.2.0)\n\n";
#endif
#ifdef USE_LINK_UNIVERSAL
std::string output = "LinkUniversal\n\n";
std::string output = "LinkUniversal_stress (v6.2.0)\n\n";
#endif
link->deactivate();
linkConnection->deactivate();
output +=
"A: Test packet loss\nB: Test packet sync\nL: Measure ping latency\nR: "
@@ -116,14 +116,14 @@ int main() {
if (initialKeys & KEY_UP)
interval = 10;
#ifndef USE_LINK_UNIVERSAL
link->config.interval = interval;
linkConnection->config.interval = interval;
#endif
#ifdef USE_LINK_UNIVERSAL
link->linkCable->config.interval = interval;
link->linkWireless->config.interval = interval;
linkConnection->linkCable->config.interval = interval;
linkConnection->linkWireless->config.interval = interval;
#endif
link->activate();
linkConnection->activate();
if (initialKeys & KEY_A)
test(false);
@@ -150,37 +150,37 @@ void test(bool withSync) {
if (needsReset())
return;
link->sync();
auto playerCount = link->playerCount();
linkConnection->sync();
auto playerCount = linkConnection->playerCount();
std::string output = "";
if (link->isConnected() && playerCount == 2) {
if (linkConnection->isConnected() && playerCount == 2) {
u16 keys = ~REG_KEYS & KEY_ANY;
if (keys & KEY_START) {
log("Lagging...");
wait(1500);
}
auto currentPlayerId = link->currentPlayerId();
auto currentPlayerId = linkConnection->currentPlayerId();
auto remotePlayerId = !currentPlayerId;
if (localCounter < FINAL_VALUE) {
localCounter++;
link->send(localCounter);
linkConnection->send(localCounter);
}
if (localCounter == 1 || withSync) {
while (link->peek(remotePlayerId) != localCounter) {
if (!link->waitFor(remotePlayerId, needsReset))
while (linkConnection->peek(remotePlayerId) != localCounter) {
if (!linkConnection->waitFor(remotePlayerId, needsReset))
return;
}
}
while (link->canRead(remotePlayerId) &&
while (linkConnection->canRead(remotePlayerId) &&
(!withSync || expectedCounter + 1 == localCounter)) {
expectedCounter++;
u16 message = link->read(remotePlayerId);
u16 message = linkConnection->read(remotePlayerId);
if (message != expectedCounter) {
error = true;
@@ -196,10 +196,11 @@ void test(bool withSync) {
if (error) {
output += "ERROR!\nExpected " + std::to_string(expectedCounter) +
" but got " + std::to_string(receivedRemoteCounter) + "\n\n";
if (link->canRead(remotePlayerId)) {
if (linkConnection->canRead(remotePlayerId)) {
output += "Remaining packets: |";
while (link->canRead(remotePlayerId))
output += std::to_string(link->read(remotePlayerId)) + "| ";
while (linkConnection->canRead(remotePlayerId))
output +=
std::to_string(linkConnection->read(remotePlayerId)) + "| ";
output += "\n\n";
}
}
@@ -241,15 +242,15 @@ void measureLatency(bool withPong) {
if (needsReset())
return;
link->sync();
auto playerCount = link->playerCount();
linkConnection->sync();
auto playerCount = linkConnection->playerCount();
if (link->isConnected() && playerCount == 2) {
auto currentPlayerId = link->currentPlayerId();
if (linkConnection->isConnected() && playerCount == 2) {
auto currentPlayerId = linkConnection->currentPlayerId();
auto remotePlayerId = !currentPlayerId;
if (!didInitialize) {
counter = 11 + link->currentPlayerId() * 10;
counter = 11 + linkConnection->currentPlayerId() * 10;
didInitialize = true;
}
@@ -258,23 +259,23 @@ void measureLatency(bool withPong) {
u32 sentPacket = ++counter;
profileStart();
link->send(sentPacket);
if (!link->waitFor(remotePlayerId, needsReset)) {
linkConnection->send(sentPacket);
if (!linkConnection->waitFor(remotePlayerId, needsReset)) {
log("No response! (1) Press DOWN");
profileStop();
waitFor(KEY_DOWN);
return;
}
u16 receivedPacket = link->read(remotePlayerId);
u16 receivedPacket = linkConnection->read(remotePlayerId);
if (withPong) {
link->send(receivedPacket);
if (!link->waitFor(remotePlayerId, needsReset)) {
linkConnection->send(receivedPacket);
if (!linkConnection->waitFor(remotePlayerId, needsReset)) {
log("No response! (2) Press DOWN");
profileStop();
waitFor(KEY_DOWN);
return;
}
u16 validation = link->read(remotePlayerId);
u16 validation = linkConnection->read(remotePlayerId);
if (validation != sentPacket) {
log("Invalid response! Press DOWN\n value = " +
std::to_string(validation) +
@@ -304,13 +305,13 @@ void measureLatency(bool withPong) {
}
void forceSync() {
auto remotePlayerId = !link->currentPlayerId();
auto remotePlayerId = !linkConnection->currentPlayerId();
link->send(10);
while (link->isConnected() && !needsReset() &&
link->peek(remotePlayerId) != 10)
link->waitFor(remotePlayerId);
link->read(remotePlayerId);
linkConnection->send(10);
while (linkConnection->isConnected() && !needsReset() &&
linkConnection->peek(remotePlayerId) != 10)
linkConnection->waitFor(remotePlayerId);
linkConnection->read(remotePlayerId);
}
void log(std::string text) {

View File

@@ -8,10 +8,10 @@
// #define USE_LINK_UNIVERSAL
#ifndef USE_LINK_UNIVERSAL
extern LinkCable* link;
extern LinkCable* linkConnection;
#endif
#ifdef USE_LINK_UNIVERSAL
extern LinkUniversal* link;
extern LinkUniversal* linkConnection;
#endif
#endif // MAIN_H

View File

@@ -27,7 +27,7 @@ int main() {
while (true) {
// (3) Use the pins
std::string output = "";
std::string output = "LinkGPIO_demo (v6.2.0)\n\n";
// Commands
u16 keys = ~REG_KEYS & KEY_ANY;

View File

@@ -33,7 +33,7 @@ int main() {
u16 prevKeys = 0;
while (true) {
std::string output = "";
std::string output = "LinkRawCable_demo (v6.2.0)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkRawCable->isActive()) {

View File

@@ -111,6 +111,14 @@ void log(std::string string) {
scrollPageDown();
}
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> toArray(
std::vector<u32> vector) {
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> array;
for (u32 i = 0; i < vector.size(); i++)
array[i] = vector[i];
return array;
}
std::vector<Background*> DebugScene::backgrounds() {
return {};
}
@@ -131,6 +139,7 @@ void DebugScene::load() {
log("---");
log("LinkRawWireless demo");
log(" (v6.2.0)");
log("");
log("START: reset wireless adapter");
log("A: send command");
@@ -472,7 +481,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
if (success) {
log("< [next slot] " +
linkRawWireless->toHex(response.nextClientNumber, 2));
for (u32 i = 0; i < response.connectedClients.size(); i++) {
for (u32 i = 0; i < response.connectedClientsSize; i++) {
log("< [client" +
std::to_string(response.connectedClients[i].clientNumber) +
"] " +
@@ -503,8 +512,8 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
log("[room.game] " + gameName);
log("[room.user] " + userName);
bool success =
linkRawWireless->broadcast(gameName, userName, (u16)gameId);
bool success = linkRawWireless->broadcast(
gameName.c_str(), userName.c_str(), (u16)gameId);
if (success)
log("NOW CALL 0x19!");
@@ -518,12 +527,22 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
"Max players?",
std::vector<std::string>{"5", "4", "3", "2"})) == -1)
;
int maxTransmissions = -1;
while ((maxTransmissions = selectU8("Max transmissions?")) == -1)
;
int waitTimeout = -1;
while ((waitTimeout = selectU8("Wait timeout?")) == -1)
;
return logOperation("sending " + name, [maxPlayers]() {
log("maxPlayers = " + std::to_string(maxPlayers));
return logOperation(
"sending " + name, [maxPlayers, maxTransmissions, waitTimeout]() {
log("maxPlayers = " + std::to_string(maxPlayers));
log("maxTransmissions = " + std::to_string(maxTransmissions));
log("waitTimeout = " + std::to_string(waitTimeout));
return linkRawWireless->setup(5 - maxPlayers);
});
return linkRawWireless->setup(5 - maxPlayers, maxTransmissions,
waitTimeout);
});
}
case 0x18:
goto generic;
@@ -537,7 +556,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
bool success = linkRawWireless->acceptConnections(response);
if (success) {
for (u32 i = 0; i < response.connectedClients.size(); i++) {
for (u32 i = 0; i < response.connectedClientsSize; i++) {
log("< [client" +
std::to_string(response.connectedClients[i].clientNumber) +
"] " +
@@ -555,7 +574,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
bool success = linkRawWireless->endHost(response);
if (success) {
for (u32 i = 0; i < response.connectedClients.size(); i++) {
for (u32 i = 0; i < response.connectedClientsSize; i++) {
log("< [client" +
std::to_string(response.connectedClients[i].clientNumber) +
"] " +
@@ -579,11 +598,12 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
}
case 0x1d: {
return logOperation("sending " + name, [this]() {
std::vector<LinkRawWireless::Server> servers;
bool success = linkRawWireless->broadcastReadPoll(servers);
LinkRawWireless::BroadcastReadPollResponse response;
bool success = linkRawWireless->broadcastReadPoll(response);
if (success) {
for (u32 i = 0; i < servers.size(); i++) {
auto servers = response.servers;
for (u32 i = 0; i < response.serversSize; i++) {
serverIds[i] = servers[i].id;
log("< [room" + std::to_string(i) + ".id] " +
@@ -598,7 +618,10 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
linkRawWireless->toHex(servers[i].nextClientNumber, 2));
}
log("NOW CALL 0x1e!");
if (response.serversSize > 0)
log("NOW CALL 0x1e!");
else
log("No rooms? NOW CALL 0x1e!");
}
return success;
@@ -660,7 +683,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
data.erase(data.begin());
return logOperation("sending " + name, [&data, bytes]() {
return linkRawWireless->sendData(data, bytes);
return linkRawWireless->sendData(toArray(data), data.size(), bytes);
});
}
case 0x25: {
@@ -672,12 +695,12 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
return logOperation("sending " + name, [&data, bytes]() {
LinkRawWireless::RemoteCommand remoteCommand;
bool success =
linkRawWireless->sendDataAndWait(data, remoteCommand, bytes);
bool success = linkRawWireless->sendDataAndWait(
toArray(data), data.size(), remoteCommand, bytes);
if (success) {
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
for (u32 i = 0; i < remoteCommand.params.size(); i++) {
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(remoteCommand.params[i]));
}
@@ -698,7 +721,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
log("< [bytesC2] " + std::to_string(response.sentBytes[3]));
log("< [bytesC3] " + std::to_string(response.sentBytes[4]));
for (u32 i = 0; i < response.data.size(); i++)
for (u32 i = 0; i < response.dataSize; i++)
log("< [data" + std::to_string(i) + "] " +
linkRawWireless->toHex(response.data[i]));
}
@@ -713,7 +736,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
if (success) {
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
for (u32 i = 0; i < remoteCommand.params.size(); i++) {
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(remoteCommand.params[i]));
}
@@ -872,8 +895,9 @@ std::string DebugScene::selectUserName() {
void DebugScene::logGenericWaitCommand(std::string name, u32 id) {
auto data = selectData();
return logOperation("sending " + name, [id, &data]() {
auto result = linkRawWireless->sendCommand(id, data);
for (u32 i = 0; i < result.responses.size(); i++) {
auto result =
linkRawWireless->sendCommand(id, toArray(data), data.size(), true);
for (u32 i = 0; i < result.responsesSize; i++) {
log("< [response" + std::to_string(i) + "] " +
linkRawWireless->toHex(result.responses[i]));
}
@@ -888,7 +912,7 @@ void DebugScene::logGenericWaitCommand(std::string name, u32 id) {
if (remoteCommand.success) {
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
for (u32 i = 0; i < remoteCommand.params.size(); i++) {
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(remoteCommand.params[i]));
}
@@ -907,8 +931,9 @@ void DebugScene::logSimpleCommand(std::string name,
u32 id,
std::vector<u32> params) {
logOperation("sending " + name, [id, &params]() {
auto result = linkRawWireless->sendCommand(id, params);
for (u32 i = 0; i < result.responses.size(); i++) {
auto result =
linkRawWireless->sendCommand(id, toArray(params), params.size());
for (u32 i = 0; i < result.responsesSize; i++) {
log("< [response" + std::to_string(i) + "] " +
linkRawWireless->toHex(result.responses[i]));
}

View File

@@ -32,7 +32,7 @@ int main() {
u32 counter = 0;
while (true) {
std::string output = "";
std::string output = "LinkSPI_demo (v6.2.0)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkSPI->isActive()) {

View File

@@ -21,10 +21,11 @@ void init() {
int main() {
init();
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\n\nhold B on start:\n -> set 2 players (wireless)");
log("LinkUniversal_basic (v6.2.0)\n\n\nPress A to start\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\n\nhold B on start:\n -> set 2 "
"players (wireless)");
waitFor(KEY_A);
u16 initialKeys = ~REG_KEYS & KEY_ANY;
bool forceCable = initialKeys & KEY_LEFT;

View File

@@ -0,0 +1,293 @@
# === SETUP ===========================================================
# --- No implicit rules ---
.SUFFIXES:
# --- Paths ---
export TONCLIB := $(DEVKITPRO)/libtonc
# === TONC RULES ======================================================
#
# Yes, this is almost, but not quite, completely like to
# DKP's base_rules and gba_rules
#
export PATH := $(DEVKITARM)/bin:$(PATH)
# --- Executable names ---
PREFIX ?= arm-none-eabi-
export CC := $(PREFIX)gcc
export CXX := $(PREFIX)g++
export AS := $(PREFIX)as
export AR := $(PREFIX)ar
export NM := $(PREFIX)nm
export OBJCOPY := $(PREFIX)objcopy
# LD defined in Makefile
# === LINK / TRANSLATE ================================================
%.gba : %.elf
@$(OBJCOPY) -O binary $< $@
@echo built ... $(notdir $@)
@gbafix $@ -t$(TITLE)
#----------------------------------------------------------------------
%.mb.elf :
@echo Linking multiboot
$(LD) -specs=gba_mb.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.elf :
@echo Linking cartridge
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
$(NM) -Sn $@ > $(basename $(notdir $@)).map
#----------------------------------------------------------------------
%.a :
@echo $(notdir $@)
@rm -f $@
$(AR) -crs $@ $^
# === OBJECTIFY =======================================================
%.iwram.o : %.iwram.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.iwram.o : %.iwram.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(IARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.cpp
@echo $(notdir $<)
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.c
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(RARCH) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.s
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
%.o : %.S
@echo $(notdir $<)
$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@
#----------------------------------------------------------------------
# canned command sequence for binary data
#----------------------------------------------------------------------
define bin2o
bin2s $< | $(AS) -o $(@)
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
endef
# =====================================================================
# === PROJECT DETAILS =================================================
# PROJ : Base project name
# TITLE : Title for ROM header (12 characters)
# LIBS : Libraries to use, formatted as list for linker flags
# BUILD : Directory for build process temporaries. Should NOT be empty!
# SRCDIRS : List of source file directories
# DATADIRS : List of data file directories
# INCDIRS : List of header file directories
# LIBDIRS : List of library directories
# General note: use `.' for the current dir, don't leave the lists empty.
export PROJ ?= $(notdir $(CURDIR))
TITLE := $(PROJ)
LIBS := -ltonc -lugba -lgba-sprite-engine
BUILD := build
SRCDIRS := src ../_lib ../../lib \
src/scenes \
src/utils \
src/utils/gbfs
DATADIRS :=
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
# --- switches ---
bMB := 0 # Multiboot build
bTEMPS := 0 # Save gcc temporaries (.i and .S files)
bDEBUG2 := 0 # Generate debug info (bDEBUG2? Not a full DEBUG flag. Yet)
# === BUILD FLAGS =====================================================
# This is probably where you can stop editing
# NOTE: I've noticed that -fgcse and -ftree-loop-optimize sometimes muck
# up things (gcse seems fond of building masks inside a loop instead of
# outside them for example). Removing them sometimes helps
# --- Architecture ---
ARCH := -mthumb-interwork -mthumb
RARCH := -mthumb-interwork -mthumb
IARCH := -mthumb-interwork -marm -mlong-calls
# --- Main flags ---
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
CFLAGS += -Wall
CFLAGS += $(INCLUDE)
CFLAGS += -ffast-math -fno-strict-aliasing
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := $(ARCH) $(INCLUDE)
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
# --- switched additions ----------------------------------------------
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
else
TARGET := $(PROJ)
endif
# --- Save temporary files ? ---
ifeq ($(strip $(bTEMPS)), 1)
CFLAGS += -save-temps
CXXFLAGS += -save-temps
endif
# --- Debug info ? ---
ifeq ($(strip $(bDEBUG)), 1)
CFLAGS += -DDEBUG -g
CXXFLAGS += -DDEBUG -g
ASFLAGS += -DDEBUG -g
LDFLAGS += -g
else
CFLAGS += -DNDEBUG
CXXFLAGS += -DNDEBUG
ASFLAGS += -DNDEBUG
endif
# --- Custom vars ? ---
GAMETITLE=test-multi-link
GAMEMAKER=AGB
GAMECODE=AZCE # Megaman Zero (SRAM - 64kb)
ENV ?= development
ifeq ($(ENV), debug)
CXXFLAGS += -DENV_DEBUG=true -DENV_DEVELOPMENT=true
else ifeq ($(ENV), development)
CXXFLAGS += -DENV_DEVELOPMENT=true
else
endif
# CXXFLAGS += -DCUSTOM_VAR_DEFINE
# === BUILD PROC ======================================================
ifneq ($(BUILD),$(notdir $(CURDIR)))
# Still in main dir:
# * Define/export some extra variables
# * Invoke this file again from the build dir
# PONDER: what happens if BUILD == "" ?
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := \
$(foreach dir, $(SRCDIRS) , $(CURDIR)/$(dir)) \
$(foreach dir, $(DATADIRS), $(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
# --- List source and data files ---
CFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir, $(SRCDIRS) , $(notdir $(wildcard $(dir)/*.S)))
BINFILES := $(foreach dir, $(DATADIRS), $(notdir $(wildcard $(dir)/*.*)))
# --- Set linker depending on C++ file existence ---
ifeq ($(strip $(CPPFILES)),)
export LD := $(CC)
else
export LD := $(CXX)
endif
# --- Define object file list ---
export OFILES := $(addsuffix .o, $(BINFILES)) \
$(CFILES:.c=.o) $(CPPFILES:.cpp=.o) \
$(SFILES:.S=.o)
# --- Create include and library search paths ---
export INCLUDE := $(foreach dir,$(INCDIRS),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := -L$(CURDIR) $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
# --- Create BUILD if necessary, and run this makefile from there ---
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
arm-none-eabi-nm -Sn $(OUTPUT).elf > $(BUILD)/$(TARGET).map
all : $(BUILD)
clean:
@echo clean ...
@rm -rf $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav
else # If we're here, we should be in the BUILD dir
DEPENDS := $(OFILES:.o=.d)
# --- Main targets ----
$(OUTPUT).gba : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
-include $(DEPENDS)
endif # End BUILD switch
# --- More targets ----------------------------------------------------
.PHONY: clean rebuild start
rebuild: clean $(BUILD) package
package: $(BUILD)
./scripts/package.sh
start: package
start "$(TARGET).out.gba"
restart: rebuild start
# EOF

Binary file not shown.

View File

@@ -0,0 +1,56 @@
#!/bin/bash
FILE_INPUT="LinkWirelessMultiboot_demo.gba"
FILE_TMP="LinkWirelessMultiboot_demo.tmp.gba"
FILE_OUTPUT="LinkWirelessMultiboot_demo.out.gba"
DATA="content.gbfs"
if [ ! -f "$DATA" ]; then
echo ""
echo "The file $DATA does not exist. Run make import first!"
echo ""
exit 1
fi
KB=$((1024))
MAX_ROM_SIZE_KB=$((32 * $KB - 1))
INITIAL_REQUIRED_SIZE_KB=256
ROM_SIZE=$(wc -c < $FILE_INPUT)
if [ $? -ne 0 ]; then
exit 1
fi
GBFS_SIZE=$(wc -c < $DATA)
if [ $? -ne 0 ]; then
exit 1
fi
ROM_SIZE_KB=$(($ROM_SIZE / $KB))
GBFS_SIZE_KB=$(($GBFS_SIZE / $KB))
MAX_REQUIRED_SIZE_KB=$(($MAX_ROM_SIZE_KB - $GBFS_SIZE_KB))
if (( $MAX_REQUIRED_SIZE_KB < $ROM_SIZE_KB )); then
echo ""
echo "[!] ERROR:"
echo "GBFS file too big."
echo ""
echo "GBFS_SIZE_KB=$GBFS_SIZE_KB"
echo "ROM_SIZE_KB=$ROM_SIZE_KB"
echo "(MAX_ROM_SIZE_KB=$MAX_ROM_SIZE_KB)"
echo ""
exit 1
fi
REQUIRED_SIZE_KB=$(($INITIAL_REQUIRED_SIZE_KB > $MAX_REQUIRED_SIZE_KB ? $MAX_REQUIRED_SIZE_KB : $INITIAL_REQUIRED_SIZE_KB))
PAD_NEEDED=$((($REQUIRED_SIZE_KB * $KB) - $ROM_SIZE))
cp $FILE_INPUT $FILE_TMP
if [ $? -ne 0 ]; then
exit 1
fi
dd if=/dev/zero bs=1 count=$PAD_NEEDED >> $FILE_TMP
if [ $? -ne 0 ]; then
exit 1
fi
cat $FILE_TMP $DATA > $FILE_OUTPUT
if [ $? -ne 0 ]; then
exit 1
fi
rm $FILE_TMP

View File

@@ -0,0 +1,47 @@
#include <libgba-sprite-engine/gba_engine.h>
#include <tonc.h>
#include "../../../lib/LinkWirelessMultiboot.hpp"
#include "../../_lib/interrupt.h"
#include "scenes/MultibootScene.h"
#include "utils/SceneUtils.h"
void setUpInterrupts();
void printTutorial();
static std::shared_ptr<GBAEngine> engine{new GBAEngine()};
static std::unique_ptr<MultibootScene> multibootScene{
new MultibootScene(engine)};
LinkWirelessMultiboot* linkWirelessMultiboot = new LinkWirelessMultiboot();
int main() {
setUpInterrupts();
REG_WAITCNT = 0x4317; // (3,1 waitstates, prefetch ON)
engine->setScene(multibootScene.get());
while (true) {
engine->update();
VBlankIntrWait();
}
return 0;
}
inline void ISR_reset() {
RegisterRamReset(RESET_REG | RESET_VRAM);
SoftReset();
}
inline void setUpInterrupts() {
interrupt_init();
interrupt_set_handler(INTR_VBLANK, [] {});
interrupt_enable(INTR_VBLANK);
// A+B+START+SELECT
REG_KEYCNT = 0b1100000000001111;
interrupt_set_handler(INTR_KEYPAD, ISR_reset);
interrupt_enable(INTR_KEYPAD);
}

View File

@@ -0,0 +1,237 @@
#include "MultibootScene.h"
#include <libgba-sprite-engine/background/text_stream.h>
#include <tonc.h>
#include <functional>
#include "../../../../lib/LinkWirelessMultiboot.hpp"
#include "utils/InputHandler.h"
#include "utils/SceneUtils.h"
extern "C" {
#include "utils/gbfs/gbfs.h"
}
static const GBFS_FILE* fs = find_first_gbfs_file(0);
const char* ROM_FILE_NAME = "rom-to-transfer.gba";
MultibootScene::MultibootScene(std::shared_ptr<GBAEngine> engine)
: Scene(engine) {}
static std::unique_ptr<InputHandler> aHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::unique_ptr<InputHandler> bHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::unique_ptr<InputHandler> upHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::unique_ptr<InputHandler> downHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::unique_ptr<InputHandler> lHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::unique_ptr<InputHandler> rHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::unique_ptr<InputHandler> selectHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::vector<std::string> logLines;
static u32 currentLogLine = 0;
static u32 players = 5;
#define MAX_LINES 20
#define DRAW_LINE 0
void printScrollableText(u32 currentLine,
std::vector<std::string> lines,
bool withCursor = false) {
for (u32 i = 0; i < MAX_LINES; i++) {
u32 lastLineIndex = MAX_LINES - 1;
u32 index = max(currentLine, lastLineIndex) - lastLineIndex + i;
if (index < lines.size()) {
std::string cursor = currentLine == index ? "> " : " ";
TextStream::instance().setText((withCursor ? cursor : "") + lines[index],
DRAW_LINE + i, -3);
} else {
TextStream::instance().setText(" ",
DRAW_LINE + i, -3);
}
}
}
void print() {
printScrollableText(currentLogLine, logLines);
}
void scrollBack() {
if (currentLogLine <= 0)
return;
currentLogLine--;
print();
}
void scrollForward() {
if (currentLogLine < MAX_LINES - 1)
currentLogLine = min(MAX_LINES - 1, logLines.size() - 1);
if (currentLogLine == logLines.size() - 1)
return;
currentLogLine++;
print();
}
void scrollPageUp() {
currentLogLine = max(currentLogLine - MAX_LINES, 0);
print();
}
void scrollPageDown() {
currentLogLine = min(currentLogLine + MAX_LINES, logLines.size() - 1);
print();
}
void scrollToTop() {
currentLogLine = 0;
print();
}
void scrollToBottom() {
currentLogLine = logLines.size() - 1;
print();
}
void clear() {
logLines.clear();
currentLogLine = 0;
print();
}
void log(std::string string) {
logLines.push_back(string);
scrollPageDown();
}
std::vector<Background*> MultibootScene::backgrounds() {
return {};
}
std::vector<Sprite*> MultibootScene::sprites() {
std::vector<Sprite*> sprites;
return sprites;
}
void MultibootScene::load() {
SCENE_init();
BACKGROUND_enable(true, false, false, false);
#ifdef LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING
linkWirelessMultiboot->logger = [](std::string string) { log(string); };
#endif
#ifdef LINK_RAW_WIRELESS_ENABLE_LOGGING
linkWirelessMultiboot->linkRawWireless->logger = [](std::string string) {
log(string);
};
#endif
log("---");
log("LinkWirelessMultiboot demo");
log(" (v6.2.0)");
log("");
if (fs == NULL) {
log("! GBFS file not found");
while (true)
;
} else if (gbfs_get_obj(fs, ROM_FILE_NAME, NULL) == NULL) {
log("! File not found in GBFS:");
log(" " + std::string(ROM_FILE_NAME));
while (true)
;
}
log("A: send ROM");
log("B: toggle players");
log("UP/DOWN: scroll up/down");
log("L/R: scroll page up/down");
log("UP+L/DOWN+R: scroll to top/bottom");
log("SELECT: clear");
log("---");
log("");
togglePlayers();
}
void MultibootScene::tick(u16 keys) {
if (engine->isTransitioning())
return;
processKeys(keys);
processButtons();
__qran_seed += keys;
__qran_seed += REG_RCNT;
__qran_seed += REG_SIOCNT;
}
void MultibootScene::processKeys(u16 keys) {
aHandler->setIsPressed(keys & KEY_A);
bHandler->setIsPressed(keys & KEY_B);
upHandler->setIsPressed(keys & KEY_UP);
downHandler->setIsPressed(keys & KEY_DOWN);
lHandler->setIsPressed(keys & KEY_L);
rHandler->setIsPressed(keys & KEY_R);
selectHandler->setIsPressed(keys & KEY_SELECT);
}
void MultibootScene::processButtons() {
if (bHandler->hasBeenPressedNow())
togglePlayers();
if (aHandler->hasBeenPressedNow()) {
u32 fileLength;
const u8* romToSend =
(const u8*)gbfs_get_obj(fs, ROM_FILE_NAME, &fileLength);
auto result = linkWirelessMultiboot->sendRom(
romToSend, fileLength, "Multiboot", "Test", 0xffff, players,
[](LinkWirelessMultiboot::MultibootProgress progress) {
return false;
});
log("-> result: " + std::to_string(result));
print();
}
if (lHandler->hasBeenPressedNow()) {
if (upHandler->getIsPressed())
scrollToTop();
else
scrollPageUp();
}
if (rHandler->hasBeenPressedNow()) {
if (downHandler->getIsPressed())
scrollToBottom();
else
scrollPageDown();
}
if (upHandler->getIsPressed())
scrollBack();
if (downHandler->getIsPressed())
scrollForward();
if (selectHandler->hasBeenPressedNow())
clear();
}
void MultibootScene::togglePlayers() {
players++;
if (players > LINK_WIRELESS_MULTIBOOT_MAX_PLAYERS)
players = LINK_WIRELESS_MULTIBOOT_MIN_PLAYERS;
log("! setting players: " + std::to_string(players));
}
void MultibootScene::logOperation(std::string name,
std::function<bool()> operation) {
log("> " + name + "...");
bool success = operation();
log(success ? "< success :)" : "< failure :(");
log("");
}

View File

@@ -0,0 +1,35 @@
#ifndef MULTIBOOT_SCENE_H
#define MULTIBOOT_SCENE_H
#include <libgba-sprite-engine/background/background.h>
#include <libgba-sprite-engine/gba_engine.h>
#include <libgba-sprite-engine/scene.h>
#include <libgba-sprite-engine/sprites/sprite.h>
#include <string>
#include <vector>
class MultibootScene : public Scene {
public:
MultibootScene(std::shared_ptr<GBAEngine> engine);
std::vector<Background*> backgrounds() override;
std::vector<Sprite*> sprites() override;
void load() override;
void tick(u16 keys) override;
private:
struct CommandMenuOption {
std::string name;
u8 command;
};
int lastSelectedCommandIndex = 0;
void processKeys(u16 keys);
void processButtons();
void togglePlayers();
void logOperation(std::string name, std::function<bool()> operation);
};
#endif // MULTIBOOT_SCENE_H

View File

@@ -0,0 +1,39 @@
#ifndef INPUT_HANDLER_H
#define INPUT_HANDLER_H
#include <libgba-sprite-engine/gba_engine.h>
class InputHandler {
public:
InputHandler() {
this->isPressed = false;
this->isWaiting = true;
}
inline bool getIsPressed() { return isPressed; }
inline bool hasBeenPressedNow() { return isNewPressEvent; }
inline bool hasBeenReleasedNow() { return isNewReleaseEvent; }
inline bool getHandledFlag() { return handledFlag; }
inline void setHandledFlag(bool value) { handledFlag = value; }
inline void setIsPressed(bool isPressed) {
bool isNewPressEvent = !this->isWaiting && !this->isPressed && isPressed;
bool isNewReleaseEvent = !this->isWaiting && this->isPressed && !isPressed;
this->isPressed = isPressed;
this->isWaiting = this->isWaiting && isPressed;
this->isNewPressEvent = isNewPressEvent;
this->isNewReleaseEvent = isNewReleaseEvent;
}
protected:
bool isPressed = false;
bool isNewPressEvent = false;
bool isNewReleaseEvent = false;
bool handledFlag = false;
bool isWaiting = false;
};
#endif // INPUT_HANDLER_H

View File

@@ -0,0 +1,52 @@
#ifndef SCENE_UTILS_H
#define SCENE_UTILS_H
#include <libgba-sprite-engine/background/text_stream.h>
#include <tonc_memdef.h>
#include <tonc_memmap.h>
#include <string>
const u32 TEXT_MIDDLE_COL = 12;
inline std::string asStr(u16 data) {
return std::to_string(data);
}
inline void BACKGROUND_enable(bool bg0, bool bg1, bool bg2, bool bg3) {
REG_DISPCNT = bg0 ? REG_DISPCNT | DCNT_BG0 : REG_DISPCNT & ~DCNT_BG0;
REG_DISPCNT = bg1 ? REG_DISPCNT | DCNT_BG1 : REG_DISPCNT & ~DCNT_BG1;
REG_DISPCNT = bg2 ? REG_DISPCNT | DCNT_BG2 : REG_DISPCNT & ~DCNT_BG2;
REG_DISPCNT = bg3 ? REG_DISPCNT | DCNT_BG3 : REG_DISPCNT & ~DCNT_BG3;
}
inline void SPRITE_disable() {
REG_DISPCNT = REG_DISPCNT & ~DCNT_OBJ;
}
inline void SCENE_init() {
TextStream::instance().clear();
TextStream::instance().scroll(0, 0);
TextStream::instance().setMosaic(false);
BACKGROUND_enable(false, false, false, false);
SPRITE_disable();
}
inline void SCENE_write(std::string text, u32 row) {
TextStream::instance().setText(text, row,
TEXT_MIDDLE_COL - text.length() / 2);
}
inline void SCENE_wait(u32 verticalLines) {
u32 count = 0;
u32 vCount = REG_VCOUNT;
while (count < verticalLines) {
if (REG_VCOUNT != vCount) {
count++;
vCount = REG_VCOUNT;
}
};
}
#endif // SCENE_UTILS_H

View File

@@ -0,0 +1,80 @@
/* gbfs.h
access object in a GBFS file
Copyright 2002 Damian Yerrick
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
/* Dependency on prior include files
Before you #include "gbfs.h", you should define the following types:
typedef (unsigned 16-bit integer) u16;
typedef (unsigned 32-bit integer) u32;
Your gba.h should do this for you.
*/
#ifndef INCLUDE_GBFS_H
#define INCLUDE_GBFS_H
#pragma GCC system_header
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
/* to make a 300 KB space called samples do GBFS_SPACE(samples, 300) */
#define GBFS_SPACE(filename, kbytes) \
const char filename[(kbytes) * 1024] __attribute__((aligned(16))) = \
"PinEightGBFSSpace-" #filename "-" #kbytes;
typedef struct GBFS_FILE {
char magic[16]; /* "PinEightGBFS\r\n\032\n" */
u32 total_len; /* total length of archive */
u16 dir_off; /* offset in bytes to directory */
u16 dir_nmemb; /* number of files */
char reserved[8]; /* for future use */
} GBFS_FILE;
typedef struct GBFS_ENTRY {
char name[24]; /* filename, nul-padded */
u32 len; /* length of object in bytes */
u32 data_offset; /* in bytes from beginning of file */
} GBFS_ENTRY;
const GBFS_FILE* find_first_gbfs_file(const void* start);
const void* skip_gbfs_file(const GBFS_FILE* file);
const void* gbfs_get_obj(const GBFS_FILE* file, const char* name, u32* len);
const void* gbfs_get_nth_obj(const GBFS_FILE* file,
size_t n,
char* name,
u32* len);
void* gbfs_copy_obj(void* dst, const GBFS_FILE* file, const char* name);
size_t gbfs_count_objs(const GBFS_FILE* file);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,155 @@
#pragma GCC diagnostic ignored "-Wstringop-truncation"
/* libgbfs.c
access object in a GBFS file
Copyright 2002-2004 Damian Yerrick
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
*/
/* This code assumes a LITTLE ENDIAN target. It'll need a boatload
of itohs and itohl calls if converted to run on Sega Genesis. It
also assumes that the target uses 16-bit short and 32-bit longs.
*/
typedef unsigned short u16;
typedef unsigned long u32;
#include <stdlib.h>
#include <string.h>
#include "gbfs.h"
/* change this to the end of your ROM, or to 0x02040000 for multiboot */
#define GBFS_1ST_SEARCH_LIMIT ((const u32*)0x02040000)
#define GBFS_2ND_SEARCH_START ((const u32*)0x08000000)
#define GBFS_2ND_SEARCH_LIMIT ((const u32*)0x0a000000)
/* a power of two, less than or equal to the argument passed to
padbin. Increasing the stride makes find_first_gbfs_file()
faster at the cost of a slightly larger binary. */
#define GBFS_ALIGNMENT 256
const GBFS_FILE* find_first_gbfs_file(const void* start) {
/* align the pointer */
const u32* here = (const u32*)((unsigned long)start & (-GBFS_ALIGNMENT));
const char rest_of_magic[] = "ightGBFS\r\n\x1a\n";
/* Linear-search first in multiboot space. */
while (here < GBFS_1ST_SEARCH_LIMIT) {
/* We have to keep the magic code in two pieces; otherwise,
this function may find itself and think it's a GBFS file.
This obviously won't work if your compiler stores this
numeric literal just before the literal string, but Devkit
Advance R4 and R5 seem to keep numeric constant pools
separate enough from string pools for this to work.
*/
if (*here == 0x456e6950) /* ASCII code for little endian "PinE" */
{
/* We've matched the first four bytes.
If the rest of the magic matches, then we've found a file. */
if (!memcmp(here + 1, rest_of_magic, 12))
return (const GBFS_FILE*)here;
}
here += GBFS_ALIGNMENT / sizeof(*here);
}
/* Now search in ROM space. */
if (here < GBFS_2ND_SEARCH_START)
here = GBFS_2ND_SEARCH_START;
while (here < GBFS_2ND_SEARCH_LIMIT) {
/* Search loop same as above. */
if (*here == 0x456e6950) /* ASCII code for little endian "PinE" */
{
if (!memcmp(here + 1, rest_of_magic, 12))
return (const GBFS_FILE*)here;
}
here += GBFS_ALIGNMENT / sizeof(*here);
}
return 0;
}
const void* skip_gbfs_file(const GBFS_FILE* file) {
return ((char*)file + file->total_len);
}
static int namecmp(const void* a, const void* b) {
return memcmp(a, b, 24);
}
const void* gbfs_get_obj(const GBFS_FILE* file, const char* name, u32* len) {
char key[24] = {0};
const GBFS_ENTRY* dirbase =
(const GBFS_ENTRY*)((const char*)file + file->dir_off);
size_t n_entries = file->dir_nmemb;
const GBFS_ENTRY* here;
strncpy(key, name, 24);
here = bsearch(key, dirbase, n_entries, sizeof(GBFS_ENTRY), namecmp);
if (!here)
return NULL;
if (len)
*len = here->len;
return (char*)file + here->data_offset;
}
const void* gbfs_get_nth_obj(const GBFS_FILE* file,
size_t n,
char* name,
u32* len) {
const GBFS_ENTRY* dirbase =
(const GBFS_ENTRY*)((const char*)file + file->dir_off);
size_t n_entries = file->dir_nmemb;
const GBFS_ENTRY* here = dirbase + n;
if (n >= n_entries)
return NULL;
if (name) {
strncpy(name, here->name, 24);
name[24] = 0;
}
if (len)
*len = here->len;
return (char*)file + here->data_offset;
}
void* gbfs_copy_obj(void* dst, const GBFS_FILE* file, const char* name) {
u32 len;
const void* src = gbfs_get_obj(file, name, &len);
if (!src)
return NULL;
memcpy(dst, src, len);
return dst;
}
size_t gbfs_count_objs(const GBFS_FILE* file) {
return file ? file->dir_nmemb : 0;
}

View File

@@ -47,8 +47,8 @@ int main() {
start:
// Options
log("Press A to start\n\n\n\n\n\n\n\n\nhold LEFT on start:\n -> "
"disable forwarding\n\nhold UP on start:\n -> disable "
log("LinkWireless_demo (v6.2.0)\n\n\n\nPress A to start\n\n\n\n\nhold LEFT "
"on start:\n -> disable forwarding\n\nhold UP on start:\n -> disable "
"retransmission\n\nhold B on start:\n -> set 2 players\n\nhold START on "
"start:\n -> async ACK");
waitFor(KEY_A);

View File

@@ -31,7 +31,9 @@ cp LinkRawCable_demo.gba ../
cd ..
cd LinkRawWireless_demo/
sed -i -e "s/\/\/ #define LINK_RAW_WIRELESS_ENABLE_LOGGING/#define LINK_RAW_WIRELESS_ENABLE_LOGGING/g" ../../lib/LinkRawWireless.hpp
make rebuild
sed -i -e "s/#define LINK_RAW_WIRELESS_ENABLE_LOGGING/\/\/ #define LINK_RAW_WIRELESS_ENABLE_LOGGING/g" ../../lib/LinkRawWireless.hpp
cp LinkRawWireless_demo.gba ../
cd ..
@@ -86,3 +88,10 @@ mv backup.gba LinkWireless_demo.gba
sed -i -e "s/#define LINK_WIRELESS_PUT_ISR_IN_IWRAM/\/\/ #define LINK_WIRELESS_PUT_ISR_IN_IWRAM/g" ../../lib/LinkWireless.hpp
sed -i -e "s/#define PROFILING_ENABLED/\/\/ #define PROFILING_ENABLED/g" ../../lib/LinkWireless.hpp
cd ..
cd LinkWirelessMultiboot_demo/
sed -i -e "s/\/\/ #define LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING/#define LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING/g" ../../lib/LinkWirelessMultiboot.hpp
make rebuild
cp LinkWirelessMultiboot_demo.out.gba ../LinkWirelessMultiboot_demo.gba
sed -i -e "s/#define LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING/\/\/ #define LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING/g" ../../lib/LinkWirelessMultiboot.hpp
cd ..

View File

@@ -67,7 +67,7 @@
#define LINK_CABLE_BIT_GENERAL_PURPOSE_HIGH 15
#define LINK_CABLE_BARRIER asm volatile("" ::: "memory")
static volatile char LINK_CABLE_VERSION[] = "LinkCable/v6.1.1";
static volatile char LINK_CABLE_VERSION[] = "LinkCable/v6.2.0";
void LINK_CABLE_ISR_VBLANK();
void LINK_CABLE_ISR_SERIAL();

View File

@@ -9,12 +9,12 @@
// LinkCableMultiboot* linkCableMultiboot = new LinkCableMultiboot();
// - 2) Send the ROM:
// LinkCableMultiboot::Result result = linkCableMultiboot->sendRom(
// romBytes, // for current ROM, use: ((const void*)MEM_EWRAM)
// romLength, // should be multiple of 0x10
// romBytes, // for current ROM, use: ((const u8*)MEM_EWRAM)
// romLength, // in bytes, should be multiple of 0x10
// []() {
// u16 keys = ~REG_KEYS & KEY_ANY;
// return keys & KEY_START;
// // (when this returns true, transfer will be canceled)
// // (when this returns true, the transfer will be canceled)
// }
// );
// // `result` should be LinkCableMultiboot::Result::SUCCESS
@@ -25,6 +25,7 @@
#include <tonc_bios.h>
#include <tonc_core.h>
#include "LinkRawCable.hpp"
#define LINK_CABLE_MULTIBOOT_MIN_ROM_SIZE (0x100 + 0xc0)
#define LINK_CABLE_MULTIBOOT_MAX_ROM_SIZE (256 * 1024)
@@ -43,11 +44,7 @@
#define LINK_CABLE_MULTIBOOT_ACK_RESPONSE 0x73
#define LINK_CABLE_MULTIBOOT_HEADER_SIZE 0xC0
#define LINK_CABLE_MULTIBOOT_SWI_MULTIPLAYER_MODE 1
#define LINK_CABLE_MULTIBOOT_SIOCNT_MAX_BAUD_RATE 3
#define LINK_CABLE_MULTIBOOT_BIT_START 7
#define LINK_CABLE_MULTIBOOT_BIT_MULTIPLAYER 13
#define LINK_CABLE_MULTIBOOT_BIT_GENERAL_PURPOSE_LOW 14
#define LINK_CABLE_MULTIBOOT_BIT_GENERAL_PURPOSE_HIGH 15
#define LINK_CABLE_MULTIBOOT_MAX_BAUD_RATE LinkRawCable::BaudRate::BAUD_RATE_3
#define LINK_CABLE_MULTIBOOT_TRY(CALL) \
do { \
partialResult = CALL; \
@@ -58,7 +55,7 @@
return error(FAILURE_DURING_HANDSHAKE);
static volatile char LINK_CABLE_MULTIBOOT_VERSION[] =
"LinkCableMultiboot/v6.1.1";
"LinkCableMultiboot/v6.2.0";
const u8 LINK_CABLE_MULTIBOOT_CLIENT_IDS[] = {0b0010, 0b0100, 0b1000};
@@ -73,7 +70,7 @@ class LinkCableMultiboot {
};
template <typename F>
Result sendRom(const void* rom, u32 romSize, F cancel) {
Result sendRom(const u8* rom, u32 romSize, F cancel) {
if (romSize < LINK_CABLE_MULTIBOOT_MIN_ROM_SIZE)
return INVALID_SIZE;
if (romSize > LINK_CABLE_MULTIBOOT_MAX_ROM_SIZE)
@@ -109,12 +106,16 @@ class LinkCableMultiboot {
int result = MultiBoot(&multiBootParameters,
LINK_CABLE_MULTIBOOT_SWI_MULTIPLAYER_MODE);
setGeneralPurposeMode();
linkRawCable->deactivate();
return result == 1 ? FAILURE_DURING_TRANSFER : SUCCESS;
}
~LinkCableMultiboot() { delete linkRawCable; }
private:
LinkRawCable* linkRawCable = new LinkRawCable();
enum PartialResult { NEEDS_RETRY, FINISHED, ABORTED, ERROR };
struct Responses {
@@ -123,17 +124,18 @@ class LinkCableMultiboot {
template <typename F>
PartialResult detectClients(MultiBootParam& multiBootParameters, F cancel) {
setMultiPlayMode();
linkRawCable->activate(LINK_CABLE_MULTIBOOT_MAX_BAUD_RATE);
for (u32 t = 0; t < LINK_CABLE_MULTIBOOT_DETECTION_TRIES; t++) {
auto responses = exchange(LINK_CABLE_MULTIBOOT_HANDSHAKE, cancel);
auto response =
linkRawCable->transfer(LINK_CABLE_MULTIBOOT_HANDSHAKE, cancel);
if (cancel())
return ABORTED;
for (u32 i = 0; i < LINK_CABLE_MULTIBOOT_CLIENTS; i++) {
if ((responses.d[i] & 0xfff0) ==
if ((response.data[1 + i] & 0xfff0) ==
LINK_CABLE_MULTIBOOT_HANDSHAKE_RESPONSE) {
auto clientId = responses.d[i] & 0xf;
auto clientId = response.data[1 + i] & 0xf;
switch (clientId) {
case 0b0010:
@@ -150,7 +152,7 @@ class LinkCableMultiboot {
}
if (multiBootParameters.client_bit == 0) {
setGeneralPurposeMode();
linkRawCable->deactivate();
wait(LINK_CABLE_MULTIBOOT_WAIT_BEFORE_RETRY);
return NEEDS_RETRY;
}
@@ -179,11 +181,11 @@ class LinkCableMultiboot {
}
template <typename F>
PartialResult sendHeader(const void* rom, F cancel) {
PartialResult sendHeader(const u8* rom, F cancel) {
u16* headerOut = (u16*)rom;
for (int i = 0; i < LINK_CABLE_MULTIBOOT_HEADER_SIZE; i += 2) {
exchange(*(headerOut++), cancel);
linkRawCable->transfer(*(headerOut++), cancel);
if (cancel())
return ABORTED;
}
@@ -196,13 +198,13 @@ class LinkCableMultiboot {
auto data =
LINK_CABLE_MULTIBOOT_SEND_PALETTE | LINK_CABLE_MULTIBOOT_PALETTE_DATA;
auto responses = exchange(data, cancel);
auto response = linkRawCable->transfer(data, cancel);
if (cancel())
return ABORTED;
for (u32 i = 0; i < LINK_CABLE_MULTIBOOT_CLIENTS; i++) {
if (responses.d[i] >> 8 == LINK_CABLE_MULTIBOOT_ACK_RESPONSE)
multiBootParameters.client_data[i] = responses.d[i] & 0xff;
if (response.data[1 + i] >> 8 == LINK_CABLE_MULTIBOOT_ACK_RESPONSE)
multiBootParameters.client_data[i] = response.data[1 + i] & 0xff;
}
for (u32 i = 0; i < LINK_CABLE_MULTIBOOT_CLIENTS; i++) {
@@ -222,12 +224,13 @@ class LinkCableMultiboot {
F cancel) {
u16 data = LINK_CABLE_MULTIBOOT_CONFIRM_HANDSHAKE_DATA |
multiBootParameters.handshake_data;
auto responses = exchange(data, cancel);
auto response = linkRawCable->transfer(data, cancel);
if (cancel())
return ABORTED;
return (responses.d[0] >> 8) == LINK_CABLE_MULTIBOOT_ACK_RESPONSE ? FINISHED
: ERROR;
return (response.data[1] >> 8) == LINK_CABLE_MULTIBOOT_ACK_RESPONSE
? FINISHED
: ERROR;
}
template <typename F>
@@ -235,7 +238,7 @@ class LinkCableMultiboot {
u16 data,
u16 expectedResponse,
F cancel) {
auto responses = exchange(data, cancel);
auto response = linkRawCable->transfer(data, cancel);
if (cancel())
return ABORTED;
@@ -244,7 +247,7 @@ class LinkCableMultiboot {
u16 expectedResponseWithId = expectedResponse | clientId;
bool isClientConnected = multiBootParameters.client_bit & clientId;
if (isClientConnected && responses.d[i] != expectedResponseWithId)
if (isClientConnected && response.data[1 + i] != expectedResponseWithId)
return ERROR;
}
@@ -252,49 +255,10 @@ class LinkCableMultiboot {
}
Result error(Result error) {
setGeneralPurposeMode();
linkRawCable->deactivate();
return error;
}
template <typename F>
Responses exchange(u16 data, F cancel) {
Responses responses;
responses.d[0] = 0xffff;
responses.d[1] = 0xffff;
responses.d[2] = 0xffff;
wait(LINK_CABLE_MULTIBOOT_WAIT_BEFORE_TRANSFER);
while (isBitHigh(LINK_CABLE_MULTIBOOT_BIT_START))
if (cancel())
return responses;
REG_SIOMLT_SEND = data;
setBitHigh(LINK_CABLE_MULTIBOOT_BIT_START);
while (isBitHigh(LINK_CABLE_MULTIBOOT_BIT_START))
if (cancel())
return responses;
for (u32 i = 0; i < 3; i++)
responses.d[i] = REG_SIOMULTI[1 + i];
return responses;
}
void setMultiPlayMode() {
REG_RCNT = REG_RCNT & ~(1 << LINK_CABLE_MULTIBOOT_BIT_GENERAL_PURPOSE_HIGH);
REG_SIOCNT = (1 << LINK_CABLE_MULTIBOOT_BIT_MULTIPLAYER);
REG_SIOCNT |= LINK_CABLE_MULTIBOOT_SIOCNT_MAX_BAUD_RATE;
REG_SIOMLT_SEND = 0;
}
void setGeneralPurposeMode() {
REG_RCNT =
(REG_RCNT & ~(1 << LINK_CABLE_MULTIBOOT_BIT_GENERAL_PURPOSE_LOW)) |
(1 << LINK_CABLE_MULTIBOOT_BIT_GENERAL_PURPOSE_HIGH);
}
void wait(u32 verticalLines) {
u32 count = 0;
u32 vCount = REG_VCOUNT;
@@ -306,10 +270,6 @@ class LinkCableMultiboot {
}
};
}
bool isBitHigh(u8 bit) { return (REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { REG_SIOCNT |= 1 << bit; }
void setBitLow(u8 bit) { REG_SIOCNT &= ~(1 << bit); }
};
extern LinkCableMultiboot* linkCableMultiboot;

View File

@@ -35,7 +35,7 @@
else \
REG &= ~(1 << BIT);
static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v6.1.1";
static volatile char LINK_GPIO_VERSION[] = "LinkGPIO/v6.2.0";
const u8 LINK_GPIO_DATA_BITS[] = {2, 3, 1, 0};
const u8 LINK_GPIO_DIRECTION_BITS[] = {6, 7, 5, 4};

View File

@@ -55,7 +55,7 @@
} \
}
static volatile char LINK_RAW_CABLE_VERSION[] = "LinkRawCable/v6.1.1";
static volatile char LINK_RAW_CABLE_VERSION[] = "LinkRawCable/v6.2.0";
class LinkRawCable {
public:

View File

@@ -10,19 +10,26 @@
#include <tonc_core.h>
#include <tonc_math.h>
#include <array>
#include <cstring>
#include "LinkGPIO.hpp"
#include "LinkSPI.hpp"
#include <string>
#include <vector>
// Enable logging (set `linkRawWireless->logger` and uncomment to enable)
// #define LINK_RAW_WIRELESS_ENABLE_LOGGING
// Enable logging (set `linkRawWireless->logger`)
#define LINK_RAW_WIRELESS_ENABLE_LOGGING true
#ifdef LINK_RAW_WIRELESS_ENABLE_LOGGING
#include <string>
#define LRWLOG(str) logger(str)
#else
#define LRWLOG(str)
#endif
#define LINK_RAW_WIRELESS_MAX_PLAYERS 5
#define LINK_RAW_WIRELESS_PING_WAIT 50
#define LINK_RAW_WIRELESS_TRANSFER_WAIT 15
#define LINK_RAW_WIRELESS_CMD_TIMEOUT 100
#define LINK_RAW_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH 30
#define LINK_RAW_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH 4
#define LINK_RAW_WIRELESS_MAX_GAME_ID 0x7fff
#define LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH 14
@@ -31,13 +38,15 @@
#define LINK_RAW_WIRELESS_COMMAND_HEADER 0x9966
#define LINK_RAW_WIRELESS_RESPONSE_ACK 0x80
#define LINK_RAW_WIRELESS_DATA_REQUEST 0x80000000
#define LINK_RAW_WIRELESS_SETUP_MAGIC 0x003c0420
#define LINK_RAW_WIRELESS_SETUP_MAX_PLAYERS_BIT 16
#define LINK_RAW_WIRELESS_SETUP_MAGIC 0x003c0000
#define LINK_RAW_WIRELESS_STILL_CONNECTING 0x01000000
#define LINK_RAW_WIRELESS_BROADCAST_LENGTH 6
#define LINK_RAW_WIRELESS_BROADCAST_RESPONSE_LENGTH \
(1 + LINK_RAW_WIRELESS_BROADCAST_LENGTH)
#define LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH 22
#define LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH 23
#define LINK_RAW_WIRELESS_MAX_SERVERS \
(LINK_RAW_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH / \
LINK_RAW_WIRELESS_BROADCAST_RESPONSE_LENGTH)
#define LINK_RAW_WIRELESS_COMMAND_HELLO 0x10
#define LINK_RAW_WIRELESS_COMMAND_SETUP 0x17
#define LINK_RAW_WIRELESS_COMMAND_BROADCAST 0x16
@@ -57,16 +66,17 @@
#define LINK_RAW_WIRELESS_COMMAND_WAIT 0x27
#define LINK_RAW_WIRELESS_COMMAND_BYE 0x3d
static volatile char LINK_RAW_WIRELESS_VERSION[] = "LinkRawWireless/v6.1.1";
static volatile char LINK_RAW_WIRELESS_VERSION[] = "LinkRawWireless/v6.2.0";
const u16 LINK_RAW_WIRELESS_LOGIN_PARTS[] = {
0x494e, 0x494e, 0x544e, 0x544e, 0x4e45, 0x4e45, 0x4f44, 0x4f44, 0x8001};
class LinkRawWireless {
typedef void (*Logger)(std::string);
public:
#ifdef LINK_RAW_WIRELESS_ENABLE_LOGGING
typedef void (*Logger)(std::string);
Logger logger = [](std::string str) {};
#endif
enum State {
NEEDS_RESET,
@@ -79,50 +89,62 @@ class LinkRawWireless {
struct CommandResult {
bool success = false;
std::vector<u32> responses = std::vector<u32>{};
u32 responses[LINK_RAW_WIRELESS_MAX_COMMAND_RESPONSE_LENGTH];
u32 responsesSize = 0;
};
struct RemoteCommand {
bool success = false;
u8 commandId = 0;
std::vector<u32> params = std::vector<u32>{};
u32 params[LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH];
u32 paramsSize = 0;
};
struct Server {
u16 id = 0;
u16 gameId;
std::string gameName;
std::string userName;
char gameName[LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH + 1];
char userName[LINK_RAW_WIRELESS_MAX_USER_NAME_LENGTH + 1];
u8 nextClientNumber;
bool isFull() { return nextClientNumber == 0xff; }
};
typedef struct {
struct ConnectedClient {
u16 deviceId = 0;
u8 clientNumber = 0;
} ConnectedClient;
};
typedef struct {
struct SlotStatusResponse {
u8 nextClientNumber = 0;
std::vector<ConnectedClient> connectedClients = {};
} SlotStatusResponse;
std::array<ConnectedClient, LINK_RAW_WIRELESS_MAX_PLAYERS>
connectedClients = {};
u32 connectedClientsSize = 0;
};
typedef struct {
std::vector<ConnectedClient> connectedClients = {};
} AcceptConnectionsResponse;
struct AcceptConnectionsResponse {
std::array<ConnectedClient, LINK_RAW_WIRELESS_MAX_PLAYERS>
connectedClients = {};
u32 connectedClientsSize = 0;
};
struct BroadcastReadPollResponse {
std::array<Server, LINK_RAW_WIRELESS_MAX_SERVERS> servers;
u32 serversSize = 0;
};
enum ConnectionPhase { STILL_CONNECTING, ERROR, SUCCESS };
typedef struct {
struct ConnectionStatus {
ConnectionPhase phase = STILL_CONNECTING;
u8 assignedClientNumber = 0;
} ConnectionStatus;
};
typedef struct {
struct ReceiveDataResponse {
u32 sentBytes[LINK_RAW_WIRELESS_MAX_PLAYERS];
std::vector<u32> data = {};
} ReceiveDataResponse;
u32 data[LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH];
u32 dataSize = 0;
};
bool isActive() { return isEnabled; }
@@ -145,48 +167,50 @@ class LinkRawWireless {
return success;
}
bool setup(u8 maxPlayers = LINK_RAW_WIRELESS_MAX_PLAYERS) {
return sendCommand(
LINK_RAW_WIRELESS_COMMAND_SETUP,
std::vector<u32>{
(u32)(LINK_RAW_WIRELESS_SETUP_MAGIC |
(((LINK_RAW_WIRELESS_MAX_PLAYERS - maxPlayers) & 0b11)
<< LINK_RAW_WIRELESS_SETUP_MAX_PLAYERS_BIT))})
.success;
bool setup(u8 maxPlayers = LINK_RAW_WIRELESS_MAX_PLAYERS,
u8 maxTransmissions = 4,
u8 waitTimeout = 32,
u32 magic = LINK_RAW_WIRELESS_SETUP_MAGIC) {
u32 config =
(u32)(magic |
(((LINK_RAW_WIRELESS_MAX_PLAYERS - maxPlayers) & 0b11) << 16) |
(maxTransmissions << 8) | waitTimeout);
return sendCommand(LINK_RAW_WIRELESS_COMMAND_SETUP, {config}, 1).success;
}
bool broadcast(std::string gameName = "",
std::string userName = "",
bool broadcast(const char* gameName = "",
const char* userName = "",
u16 gameId = LINK_RAW_WIRELESS_MAX_GAME_ID) {
if (gameName.length() > LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH) {
log("! game name too long");
if (std::strlen(gameName) > LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH) {
LRWLOG("! game name too long");
return false;
}
if (userName.length() > LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH) {
log("! user name too long");
if (std::strlen(userName) > LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH) {
LRWLOG("! user name too long");
return false;
}
gameName.append(LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH - gameName.length(),
0);
userName.append(LINK_RAW_WIRELESS_MAX_USER_NAME_LENGTH - userName.length(),
0);
auto broadcastData =
std::vector<u32>{buildU32(buildU16(gameName[1], gameName[0]),
gameId & LINK_RAW_WIRELESS_MAX_GAME_ID),
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]))};
char finalGameName[LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH + 1];
char finalUserName[LINK_RAW_WIRELESS_MAX_USER_NAME_LENGTH + 1];
copyName(finalGameName, gameName, LINK_RAW_WIRELESS_MAX_GAME_NAME_LENGTH);
copyName(finalUserName, userName, LINK_RAW_WIRELESS_MAX_USER_NAME_LENGTH);
bool success =
sendCommand(LINK_RAW_WIRELESS_COMMAND_BROADCAST, broadcastData).success;
sendCommand(
LINK_RAW_WIRELESS_COMMAND_BROADCAST,
{buildU32(buildU16(finalGameName[1], finalGameName[0]), gameId),
buildU32(buildU16(finalGameName[5], finalGameName[4]),
buildU16(finalGameName[3], finalGameName[2])),
buildU32(buildU16(finalGameName[9], finalGameName[8]),
buildU16(finalGameName[7], finalGameName[6])),
buildU32(buildU16(finalGameName[13], finalGameName[12]),
buildU16(finalGameName[11], finalGameName[10])),
buildU32(buildU16(finalUserName[3], finalUserName[2]),
buildU16(finalUserName[1], finalUserName[0])),
buildU32(buildU16(finalUserName[7], finalUserName[6]),
buildU16(finalUserName[5], finalUserName[4]))},
LINK_RAW_WIRELESS_BROADCAST_LENGTH)
.success;
if (!success) {
reset();
@@ -205,7 +229,7 @@ class LinkRawWireless {
}
wait(LINK_RAW_WIRELESS_TRANSFER_WAIT);
log("state = SERVING");
LRWLOG("state = SERVING");
state = SERVING;
return true;
@@ -219,13 +243,14 @@ class LinkRawWireless {
return false;
}
for (u32 i = 0; i < result.responses.size(); i++) {
response.connectedClientsSize = 0;
for (u32 i = 0; i < result.responsesSize; i++) {
if (i == 0) {
response.nextClientNumber = (u8)lsB32(result.responses[i]);
} else {
response.connectedClients.push_back(
response.connectedClients[response.connectedClientsSize++] =
ConnectedClient{.deviceId = lsB32(result.responses[i]),
.clientNumber = (u8)msB32(result.responses[i])});
.clientNumber = (u8)msB32(result.responses[i])};
}
}
@@ -240,16 +265,17 @@ class LinkRawWireless {
return false;
}
for (u32 i = 0; i < result.responses.size(); i++) {
response.connectedClients.push_back(
response.connectedClientsSize = 0;
for (u32 i = 0; i < result.responsesSize; i++) {
response.connectedClients[response.connectedClientsSize++] =
ConnectedClient{.deviceId = lsB32(result.responses[i]),
.clientNumber = (u8)msB32(result.responses[i])});
.clientNumber = (u8)msB32(result.responses[i])};
}
u8 oldPlayerCount = sessionState.playerCount;
sessionState.playerCount = 1 + result.responses.size();
sessionState.playerCount = 1 + result.responsesSize;
if (sessionState.playerCount != oldPlayerCount)
log("now: " + std::to_string(sessionState.playerCount) + " players");
LRWLOG("now: " + std::to_string(sessionState.playerCount) + " players");
return true;
}
@@ -262,16 +288,17 @@ class LinkRawWireless {
return false;
}
for (u32 i = 0; i < result.responses.size(); i++) {
response.connectedClients.push_back(
response.connectedClientsSize = 0;
for (u32 i = 0; i < result.responsesSize; i++) {
response.connectedClients[response.connectedClientsSize++] =
ConnectedClient{.deviceId = lsB32(result.responses[i]),
.clientNumber = (u8)msB32(result.responses[i])});
.clientNumber = (u8)msB32(result.responses[i])};
}
u8 oldPlayerCount = sessionState.playerCount;
sessionState.playerCount = 1 + result.responses.size();
sessionState.playerCount = 1 + result.responsesSize;
if (sessionState.playerCount != oldPlayerCount)
log("now: " + std::to_string(sessionState.playerCount) + " players");
LRWLOG("now: " + std::to_string(sessionState.playerCount) + " players");
return true;
}
@@ -285,18 +312,17 @@ class LinkRawWireless {
return false;
}
log("state = SEARCHING");
LRWLOG("state = SEARCHING");
state = SEARCHING;
return true;
}
bool broadcastReadPoll(std::vector<Server>& servers) {
bool broadcastReadPoll(BroadcastReadPollResponse& response) {
auto result = sendCommand(LINK_RAW_WIRELESS_COMMAND_BROADCAST_READ_POLL);
bool success =
result.success &&
result.responses.size() % LINK_RAW_WIRELESS_BROADCAST_RESPONSE_LENGTH ==
0;
result.responsesSize % LINK_RAW_WIRELESS_BROADCAST_RESPONSE_LENGTH == 0;
if (!success) {
reset();
@@ -304,8 +330,9 @@ class LinkRawWireless {
}
u32 totalBroadcasts =
result.responses.size() / LINK_RAW_WIRELESS_BROADCAST_RESPONSE_LENGTH;
result.responsesSize / LINK_RAW_WIRELESS_BROADCAST_RESPONSE_LENGTH;
response.serversSize = 0;
for (u32 i = 0; i < totalBroadcasts; i++) {
u32 start = LINK_RAW_WIRELESS_BROADCAST_RESPONSE_LENGTH * i;
@@ -313,20 +340,20 @@ class LinkRawWireless {
server.id = (u16)result.responses[start];
server.gameId =
result.responses[start + 1] & LINK_RAW_WIRELESS_MAX_GAME_ID;
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]);
u32 gameI = 0, userI = 0;
recoverName(server.gameName, gameI, result.responses[start + 1], false);
recoverName(server.gameName, gameI, result.responses[start + 2]);
recoverName(server.gameName, gameI, result.responses[start + 3]);
recoverName(server.gameName, gameI, result.responses[start + 4]);
recoverName(server.userName, userI, result.responses[start + 5]);
recoverName(server.userName, userI, result.responses[start + 6]);
server.gameName[gameI] = '\0';
server.userName[userI] = '\0';
server.nextClientNumber = (result.responses[start] >> 16) & 0xff;
servers.push_back(server);
response.servers[response.serversSize++] = server;
}
log("state = AUTHENTICATED");
state = AUTHENTICATED;
return true;
}
@@ -339,20 +366,22 @@ class LinkRawWireless {
return false;
}
LRWLOG("state = AUTHENTICATED");
state = AUTHENTICATED;
return true;
}
bool connect(u16 serverId) {
bool success = sendCommand(LINK_RAW_WIRELESS_COMMAND_CONNECT,
std::vector<u32>{serverId})
.success;
bool success =
sendCommand(LINK_RAW_WIRELESS_COMMAND_CONNECT, {serverId}, 1).success;
if (!success) {
reset();
return false;
}
log("state = CONNECTING");
LRWLOG("state = CONNECTING");
state = CONNECTING;
return true;
@@ -360,9 +389,9 @@ class LinkRawWireless {
bool keepConnecting(ConnectionStatus& response) {
auto result = sendCommand(LINK_RAW_WIRELESS_COMMAND_IS_FINISHED_CONNECT);
if (!result.success || result.responses.size() == 0) {
if (result.responses.size() == 0)
log("! empty response");
if (!result.success || result.responsesSize == 0) {
if (result.responsesSize == 0)
LRWLOG("! empty response");
reset();
return false;
}
@@ -374,7 +403,7 @@ class LinkRawWireless {
u8 assignedPlayerId = 1 + (u8)msB32(result.responses[0]);
if (assignedPlayerId >= LINK_RAW_WIRELESS_MAX_PLAYERS) {
log("! connection failed (1)");
LRWLOG("! connection failed (1)");
reset();
response.phase = ERROR;
return false;
@@ -388,38 +417,45 @@ class LinkRawWireless {
bool finishConnection() {
auto result = sendCommand(LINK_RAW_WIRELESS_COMMAND_FINISH_CONNECTION);
if (!result.success || result.responses.size() == 0) {
if (result.responses.size() == 0)
log("! empty response");
if (!result.success || result.responsesSize == 0) {
if (result.responsesSize == 0)
LRWLOG("! empty response");
reset();
return false;
}
u16 status = msB32(result.responses[0]);
if ((msB16(status) & 1) == 1) {
log("! connection failed (2)");
LRWLOG("! connection failed (2)");
reset();
return false;
}
u8 assignedPlayerId = 1 + (u8)status;
sessionState.currentPlayerId = assignedPlayerId;
log("state = CONNECTED");
LRWLOG("state = CONNECTED");
state = CONNECTED;
return true;
}
bool sendData(std::vector<u32> data, u32 _bytes = 0) {
u32 bytes = _bytes == 0 ? data.size() * 4 : _bytes;
bool sendData(
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> data,
u32 dataSize,
u32 _bytes = 0) {
u32 bytes = _bytes == 0 ? dataSize * 4 : _bytes;
u32 header = sessionState.currentPlayerId == 0
? bytes
: (bytes << (3 + sessionState.currentPlayerId * 5));
data.insert(data.begin(), header);
log("using header " + toHex(header));
for (u32 i = dataSize; i > 0; i--)
data[i] = data[i - 1];
data[0] = header;
dataSize++;
LRWLOG("using header " + toHex(header));
bool success =
sendCommand(LINK_RAW_WIRELESS_COMMAND_SEND_DATA, data).success;
sendCommand(LINK_RAW_WIRELESS_COMMAND_SEND_DATA, data, dataSize)
.success;
if (!success) {
reset();
@@ -429,17 +465,23 @@ class LinkRawWireless {
return true;
}
bool sendDataAndWait(std::vector<u32> data,
RemoteCommand& remoteCommand,
u32 _bytes = 0) {
u32 bytes = _bytes == 0 ? data.size() * 4 : _bytes;
bool sendDataAndWait(
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> data,
u32 dataSize,
RemoteCommand& remoteCommand,
u32 _bytes = 0) {
u32 bytes = _bytes == 0 ? dataSize * 4 : _bytes;
u32 header = sessionState.currentPlayerId == 0
? bytes
: (bytes << (3 + sessionState.currentPlayerId * 5));
data.insert(data.begin(), header);
log("using header " + toHex(header));
for (u32 i = dataSize; i > 0; i--)
data[i] = data[i - 1];
data[0] = header;
dataSize++;
LRWLOG("using header " + toHex(header));
if (!sendCommand(LINK_RAW_WIRELESS_COMMAND_SEND_DATA_AND_WAIT, data)
if (!sendCommand(LINK_RAW_WIRELESS_COMMAND_SEND_DATA_AND_WAIT, data,
dataSize, true)
.success) {
reset();
return false;
@@ -452,7 +494,9 @@ class LinkRawWireless {
bool receiveData(ReceiveDataResponse& response) {
auto result = sendCommand(LINK_RAW_WIRELESS_COMMAND_RECEIVE_DATA);
response.data = result.responses;
for (u32 i = 0; i < result.responsesSize; i++)
response.data[i] = result.responses[i];
response.dataSize = result.responsesSize;
if (!result.success) {
reset();
@@ -462,9 +506,11 @@ class LinkRawWireless {
for (u32 i = 0; i < LINK_RAW_WIRELESS_MAX_PLAYERS; i++)
response.sentBytes[i] = 0;
if (response.data.size() > 0) {
if (response.dataSize > 0) {
u32 header = response.data[0];
response.data.erase(response.data.begin());
for (u32 i = 1; i < response.dataSize; i++)
response.data[i - 1] = response.data[i];
response.dataSize--;
response.sentBytes[0] = header & 0b1111111;
response.sentBytes[1] = (header >> 8) & 0b11111;
response.sentBytes[2] = (header >> 13) & 0b11111;
@@ -476,7 +522,7 @@ class LinkRawWireless {
}
bool wait(RemoteCommand& remoteCommand) {
if (!sendCommand(LINK_RAW_WIRELESS_COMMAND_WAIT).success) {
if (!sendCommand(LINK_RAW_WIRELESS_COMMAND_WAIT, {}, 0, true).success) {
reset();
return false;
}
@@ -486,23 +532,27 @@ class LinkRawWireless {
return remoteCommand.success;
}
CommandResult sendCommand(u8 type,
std::vector<u32> params = std::vector<u32>{}) {
CommandResult sendCommand(
u8 type,
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> params =
{},
u16 length = 0,
bool invertsClock = false) {
CommandResult result;
u16 length = params.size();
u32 command = buildCommand(type, length);
u32 r;
log("sending command 0x" + toHex(command));
LRWLOG("sending command 0x" + toHex(command));
if ((r = transfer(command)) != LINK_RAW_WIRELESS_DATA_REQUEST) {
logExpectedButReceived(LINK_RAW_WIRELESS_DATA_REQUEST, r);
return result;
}
u32 parameterCount = 0;
for (auto& param : params) {
log("sending param" + std::to_string(parameterCount) + ": 0x" +
toHex(param));
for (u32 i = 0; i < length; i++) {
u32 param = params[i];
LRWLOG("sending param" + std::to_string(parameterCount) + ": 0x" +
toHex(param));
if ((r = transfer(param)) != LINK_RAW_WIRELESS_DATA_REQUEST) {
logExpectedButReceived(LINK_RAW_WIRELESS_DATA_REQUEST, r);
return result;
@@ -510,37 +560,44 @@ class LinkRawWireless {
parameterCount++;
}
log("sending response request");
u32 response = transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
LRWLOG("sending response request");
u32 response =
invertsClock
? transferAndStartClockInversionACK(LINK_RAW_WIRELESS_DATA_REQUEST)
: transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
u16 header = msB32(response);
u16 data = lsB32(response);
u8 responses = msB16(data);
u8 ack = lsB16(data);
if (header != LINK_RAW_WIRELESS_COMMAND_HEADER) {
log("! expected HEADER 0x9966");
log("! but received 0x" + toHex(header));
LRWLOG("! expected HEADER 0x9966");
LRWLOG("! but received 0x" + toHex(header));
return result;
}
if (ack != type + LINK_RAW_WIRELESS_RESPONSE_ACK) {
if (ack == 0xee && responses == 1) {
u8 code = (u8)transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
log("! error received");
log(code == 1 ? "! invalid state" : "! unknown cmd");
if (ack == 0xee && responses == 1 && !invertsClock) {
u8 __attribute__((unused)) code =
(u8)transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
LRWLOG("! error received");
LRWLOG(code == 1 ? "! invalid state" : "! unknown cmd");
} else {
log("! expected ACK 0x" + toHex(type + LINK_RAW_WIRELESS_RESPONSE_ACK));
log("! but received 0x" + toHex(ack));
LRWLOG("! expected ACK 0x" +
toHex(type + LINK_RAW_WIRELESS_RESPONSE_ACK));
LRWLOG("! but received 0x" + toHex(ack));
}
return result;
}
log("ack ok! " + std::to_string(responses) + " responses");
LRWLOG("ack ok! " + std::to_string(responses) + " responses");
for (u32 i = 0; i < responses; i++) {
log("response " + std::to_string(i + 1) + "/" +
std::to_string(responses) + ":");
u32 responseData = transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
result.responses.push_back(responseData);
log("<< " + toHex(responseData));
if (!invertsClock) {
for (u32 i = 0; i < responses; i++) {
LRWLOG("response " + std::to_string(i + 1) + "/" +
std::to_string(responses) + ":");
u32 responseData = transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
result.responses[result.responsesSize++] = responseData;
LRWLOG("<< " + toHex(responseData));
}
}
result.success = true;
@@ -550,11 +607,12 @@ class LinkRawWireless {
RemoteCommand receiveCommandFromAdapter() {
RemoteCommand remoteCommand;
log("setting SPI to SLAVE");
LRWLOG("setting SPI to SLAVE");
linkSPI->activate(LinkSPI::Mode::SLAVE);
log("WAITING for adapter cmd");
u32 command = linkSPI->transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
LRWLOG("WAITING for adapter cmd");
u32 command = linkSPI->transfer(
LINK_RAW_WIRELESS_DATA_REQUEST, []() { return false; }, false, true);
if (!reverseAcknowledge()) {
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
reset();
@@ -566,47 +624,52 @@ class LinkRawWireless {
u8 params = msB16(data);
u8 commandId = lsB16(data);
if (header != LINK_RAW_WIRELESS_COMMAND_HEADER) {
log("! expected HEADER 0x9966");
log("! but received 0x" + toHex(header));
LRWLOG("! expected HEADER 0x9966");
LRWLOG("! but received 0x" + toHex(header));
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
reset();
return remoteCommand;
}
log("received cmd: " + toHex(commandId) + " (" + std::to_string(params) +
" params)");
LRWLOG("received cmd: " + toHex(commandId) + " (" + std::to_string(params) +
" params)");
for (u32 i = 0; i < params; i++) {
log("param " + std::to_string(i + 1) + "/" + std::to_string(params) +
":");
u32 paramData = linkSPI->transfer(LINK_RAW_WIRELESS_DATA_REQUEST);
LRWLOG("param " + std::to_string(i + 1) + "/" + std::to_string(params) +
":");
u32 paramData = linkSPI->transfer(
LINK_RAW_WIRELESS_DATA_REQUEST, []() { return false; }, false, true);
if (!reverseAcknowledge()) {
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
reset();
return remoteCommand;
}
remoteCommand.params.push_back(paramData);
log("<< " + toHex(paramData));
remoteCommand.params[remoteCommand.paramsSize++] = paramData;
LRWLOG("<< " + toHex(paramData));
}
log("sending ack");
command = linkSPI->transfer(0x99660000 | ((commandId + 0x80) & 0xff));
if (!reverseAcknowledge()) {
LRWLOG("sending ack");
command = linkSPI->transfer(
0x99660000 | ((commandId + 0x80) & 0xff), []() { return false; }, false,
true);
if (!reverseAcknowledge(true)) {
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
reset();
return remoteCommand;
}
if (command != LINK_RAW_WIRELESS_DATA_REQUEST) {
log("! expected cmd request");
log("! but received 0x" + toHex(command));
LRWLOG("! expected CMD request");
LRWLOG("! but received 0x" + toHex(command));
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
reset();
return remoteCommand;
}
log("setting SPI to 2Mbps");
LRWLOG("setting SPI to 2Mbps");
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
wait(LINK_RAW_WIRELESS_TRANSFER_WAIT);
remoteCommand.success = true;
remoteCommand.commandId = commandId;
@@ -645,24 +708,35 @@ class LinkRawWireless {
State state = NEEDS_RESET;
volatile bool isEnabled = false;
void recoverName(std::string& name,
void copyName(char* target, const char* source, u32 length) {
u32 len = std::strlen(source);
for (u32 i = 0; i < length + 1; i++)
if (i < len)
target[i] = source[i];
else
target[i] = '\0';
}
void recoverName(char* name,
u32& nameCursor,
u32 word,
bool includeFirstTwoBytes = true) {
u32 character = 0;
if (includeFirstTwoBytes) {
character = lsB16(lsB32(word));
if (character > 0)
name.push_back(character);
name[nameCursor++] = character;
character = msB16(lsB32(word));
if (character > 0)
name.push_back(character);
name[nameCursor++] = character;
}
character = lsB16(msB32(word));
if (character > 0)
name.push_back(character);
name[nameCursor++] = character;
character = msB16(msB32(word));
if (character > 0)
name.push_back(character);
name[nameCursor++] = character;
}
bool reset(bool initialize = false) {
@@ -673,7 +747,7 @@ class LinkRawWireless {
}
void resetState() {
log("state = NEEDS_RESET");
LRWLOG("state = NEEDS_RESET");
this->state = NEEDS_RESET;
this->sessionState.playerCount = 1;
this->sessionState.currentPlayerId = 0;
@@ -683,7 +757,7 @@ class LinkRawWireless {
bool start() {
pingAdapter();
log("setting SPI to 256Kbps");
LRWLOG("setting SPI to 256Kbps");
linkSPI->activate(LinkSPI::Mode::MASTER_256KBPS);
if (!login())
@@ -691,40 +765,40 @@ class LinkRawWireless {
wait(LINK_RAW_WIRELESS_TRANSFER_WAIT);
log("sending HELLO command");
LRWLOG("sending HELLO command");
if (!sendCommand(LINK_RAW_WIRELESS_COMMAND_HELLO).success)
return false;
log("setting SPI to 2Mbps");
LRWLOG("setting SPI to 2Mbps");
linkSPI->activate(LinkSPI::Mode::MASTER_2MBPS);
log("state = AUTHENTICATED");
LRWLOG("state = AUTHENTICATED");
state = AUTHENTICATED;
return true;
}
void pingAdapter() {
log("setting SO as OUTPUT");
LRWLOG("setting SO as OUTPUT");
linkGPIO->setMode(LinkGPIO::Pin::SO, LinkGPIO::Direction::OUTPUT);
log("setting SD as OUTPUT");
LRWLOG("setting SD as OUTPUT");
linkGPIO->setMode(LinkGPIO::Pin::SD, LinkGPIO::Direction::OUTPUT);
log("setting SD = HIGH");
LRWLOG("setting SD = HIGH");
linkGPIO->writePin(LinkGPIO::SD, true);
wait(LINK_RAW_WIRELESS_PING_WAIT);
log("setting SD = LOW");
LRWLOG("setting SD = LOW");
linkGPIO->writePin(LinkGPIO::SD, false);
}
bool login() {
LoginMemory memory;
log("sending initial login packet");
LRWLOG("sending initial login packet");
if (!exchangeLoginPacket(LINK_RAW_WIRELESS_LOGIN_PARTS[0], 0, memory))
return false;
for (u32 i = 0; i < LINK_RAW_WIRELESS_LOGIN_STEPS; i++) {
log("sending login packet " + std::to_string(i + 1) + "/" +
std::to_string(LINK_RAW_WIRELESS_LOGIN_STEPS));
LRWLOG("sending login packet " + std::to_string(i + 1) + "/" +
std::to_string(LINK_RAW_WIRELESS_LOGIN_STEPS));
if (!exchangeLoginPacket(LINK_RAW_WIRELESS_LOGIN_PARTS[i],
LINK_RAW_WIRELESS_LOGIN_PARTS[i], memory))
return false;
@@ -772,6 +846,19 @@ class LinkRawWireless {
return receivedData;
}
u32 transferAndStartClockInversionACK(u32 data) {
u32 lines = 0;
u32 vCount = REG_VCOUNT;
u32 receivedData = linkSPI->transfer(
data, [this, &lines, &vCount]() { return cmdTimeout(lines, vCount); },
false, true);
if (!reverseAcknowledgeStart())
return LINK_SPI_NO_DATA;
return receivedData;
}
bool acknowledge() {
u32 lines = 0;
u32 vCount = REG_VCOUNT;
@@ -779,16 +866,16 @@ class LinkRawWireless {
linkSPI->_setSOLow();
while (!linkSPI->_isSIHigh()) {
if (cmdTimeout(lines, vCount)) {
log("! ACK 1 failed. I put SO=LOW,");
log("! but SI didn't become HIGH.");
LRWLOG("! ACK 1 failed. I put SO=LOW,");
LRWLOG("! but SI didn't become HIGH.");
return false;
}
}
linkSPI->_setSOHigh();
while (linkSPI->_isSIHigh()) {
if (cmdTimeout(lines, vCount)) {
log("! ACK 2 failed. I put SO=HIGH,");
log("! but SI didn't become LOW.");
LRWLOG("! ACK 2 failed. I put SO=HIGH,");
LRWLOG("! but SI didn't become LOW.");
return false;
}
}
@@ -797,17 +884,57 @@ class LinkRawWireless {
return true;
}
bool reverseAcknowledge() {
bool reverseAcknowledgeStart() {
u32 lines = 0;
u32 vCount = REG_VCOUNT;
linkSPI->_setSOLow();
wait(1);
linkSPI->_setSOHigh();
while (linkSPI->_isSIHigh()) {
if (cmdTimeout(lines, vCount)) {
LRWLOG("! Rev0 failed. I put SO=HIGH,");
LRWLOG("! but SI didn't become LOW.");
return false;
}
}
linkSPI->_setSOLow();
return true;
}
bool reverseAcknowledge(bool isLastPart = false) {
u32 lines = 0;
u32 vCount = REG_VCOUNT;
linkSPI->_setSOLow();
while (linkSPI->_isSIHigh()) {
if (cmdTimeout(lines, vCount)) {
LRWLOG("! RevAck0 failed. I put SO=LOW,");
LRWLOG("! but SI didn't become LOW.");
return false;
}
}
linkSPI->_setSOHigh();
while (!linkSPI->_isSIHigh()) {
if (cmdTimeout(lines, vCount)) {
log("! REV_ACK failed. I put SO=HIGH,");
log("! but SI didn't become HIGH.");
LRWLOG("! RevAck1 failed. I put SO=HIGH,");
LRWLOG("! but SI didn't become HIGH.");
return false;
}
}
// (normally, this occurs on the next linkSPI->transfer(...) call)
if (isLastPart) {
linkSPI->_setSOLow();
while (linkSPI->_isSIHigh()) {
if (cmdTimeout(lines, vCount)) {
LRWLOG("! RevAck2 failed. I put SO=LOW,");
LRWLOG("! but SI didn't become LOW.");
return false;
}
}
}
return true;
}
@@ -838,10 +965,11 @@ class LinkRawWireless {
}
void logExpectedButReceived(u32 expected, u32 received) {
log("! expected 0x" + toHex(expected));
log("! but received 0x" + toHex(received));
LRWLOG("! expected 0x" + toHex(expected));
LRWLOG("! but received 0x" + toHex(received));
}
#ifdef LINK_RAW_WIRELESS_ENABLE_LOGGING
template <typename I>
std::string toHex(I w, size_t hex_len = sizeof(I) << 1) {
static const char* digits = "0123456789ABCDEF";
@@ -850,11 +978,7 @@ class LinkRawWireless {
rc[i] = digits[(w >> j) & 0x0f];
return rc;
}
inline __attribute__((always_inline)) void log(std::string str) {
if (LINK_RAW_WIRELESS_ENABLE_LOGGING)
logger(str);
}
#endif
u32 buildU32(u16 msB, u16 lsB) { return (msB << 16) | lsB; }
u16 buildU16(u8 msB, u8 lsB) { return (msB << 8) | lsB; }
@@ -866,4 +990,6 @@ class LinkRawWireless {
extern LinkRawWireless* linkRawWireless;
#undef LRWLOG
#endif // LINK_RAW_WIRELESS_H

View File

@@ -42,7 +42,30 @@
#include <tonc_core.h>
// 8-bit mode (uncomment to enable)
// #define LINK_SPI_8BIT_MODE
#ifdef LINK_SPI_8BIT_MODE
#define LINK_SPI_DATA_TYPE u8
#endif
#ifndef LINK_SPI_8BIT_MODE
#define LINK_SPI_DATA_TYPE u32
#endif
#ifdef LINK_SPI_8BIT_MODE
#define LINK_SPI_DATA_REG REG_SIODATA8
#endif
#ifndef LINK_SPI_8BIT_MODE
#define LINK_SPI_DATA_REG REG_SIODATA32
#endif
#ifdef LINK_SPI_8BIT_MODE
#define LINK_SPI_NO_DATA 0xff
#endif
#ifndef LINK_SPI_8BIT_MODE
#define LINK_SPI_NO_DATA 0xffffffff
#endif
#define LINK_SPI_BIT_CLOCK 0
#define LINK_SPI_BIT_CLOCK_SPEED 1
#define LINK_SPI_BIT_SI 2
@@ -53,7 +76,10 @@
#define LINK_SPI_BIT_GENERAL_PURPOSE_LOW 14
#define LINK_SPI_BIT_GENERAL_PURPOSE_HIGH 15
static volatile char LINK_SPI_VERSION[] = "LinkSPI/v6.1.1";
static volatile char LINK_SPI_VERSION[] = "LinkSPI/v6.2.0";
const u32 LINK_SPI_MASK_CLEAR_SO_BIT = ~(1 << LINK_SPI_BIT_SO);
const u32 LINK_SPI_MASK_SET_START_BIT = (1 << LINK_SPI_BIT_START);
class LinkSPI {
public:
@@ -95,15 +121,15 @@ class LinkSPI {
asyncData = 0;
}
u32 transfer(u32 data) {
LINK_SPI_DATA_TYPE transfer(LINK_SPI_DATA_TYPE data) {
return transfer(data, []() { return false; });
}
template <typename F>
u32 transfer(u32 data,
F cancel,
bool _async = false,
bool _customAck = false) {
LINK_SPI_DATA_TYPE transfer(LINK_SPI_DATA_TYPE data,
F cancel,
bool _async = false,
bool _customAck = false) {
if (asyncState != IDLE)
return LINK_SPI_NO_DATA;
@@ -116,8 +142,6 @@ class LinkSPI {
setInterruptsOff();
}
enableTransfer();
while (isMaster() && waitMode && !isSlaveReady())
if (cancel()) {
disableTransfer();
@@ -126,7 +150,22 @@ class LinkSPI {
return LINK_SPI_NO_DATA;
}
startTransfer();
asm volatile(
// enableTransfer();
// startTransfer();
"MOV R2, %[reg_siocnt]\n\t" // Move &REG_SIOCNT to R2
"LDR R0, [R2]\n\t" // Load SIOCNT into R0
"LDR R1, %[clear_so_bit_mask]\n\t" // Load mask value to clear SO bit
"AND R0, R0, R1\n\t" // Clear SO bit
"STR R0, [R2]\n\t" // Store back to SIOCNT
"LDR R1, %[set_start_bit_mask]\n\t" // Load mask value to set START bit
"ORR R0, R0, R1\n\t" // Set START bit
"STR R0, [R2]\n\t" // Store back to SIOCNT
:
: [reg_siocnt] "r"(&REG_SIOCNT),
[clear_so_bit_mask] "m"(LINK_SPI_MASK_CLEAR_SO_BIT),
[set_start_bit_mask] "m"(LINK_SPI_MASK_SET_START_BIT)
: "r0", "r1", "r2");
if (_async)
return LINK_SPI_NO_DATA;
@@ -144,21 +183,21 @@ class LinkSPI {
return getData();
}
void transferAsync(u32 data) {
void transferAsync(LINK_SPI_DATA_TYPE data) {
transfer(
data, []() { return false; }, true);
}
template <typename F>
void transferAsync(u32 data, F cancel) {
void transferAsync(LINK_SPI_DATA_TYPE data, F cancel) {
transfer(data, cancel, true);
}
u32 getAsyncData() {
LINK_SPI_DATA_TYPE getAsyncData() {
if (asyncState != READY)
return LINK_SPI_NO_DATA;
u32 data = asyncData;
LINK_SPI_DATA_TYPE data = asyncData;
asyncState = IDLE;
return data;
}
@@ -188,12 +227,17 @@ class LinkSPI {
Mode mode = Mode::SLAVE;
bool waitMode = false;
AsyncState asyncState = IDLE;
u32 asyncData = 0;
LINK_SPI_DATA_TYPE asyncData = 0;
volatile bool isEnabled = false;
void setNormalMode32Bit() {
REG_RCNT = REG_RCNT & ~(1 << LINK_SPI_BIT_GENERAL_PURPOSE_HIGH);
#ifdef LINK_SPI_8BIT_MODE
REG_SIOCNT = 0;
#endif
#ifndef LINK_SPI_8BIT_MODE
REG_SIOCNT = 1 << LINK_SPI_BIT_LENGTH;
#endif
}
void setGeneralPurposeMode() {
@@ -201,8 +245,8 @@ class LinkSPI {
(1 << LINK_SPI_BIT_GENERAL_PURPOSE_HIGH);
}
void setData(u32 data) { REG_SIODATA32 = data; }
u32 getData() { return REG_SIODATA32; }
void setData(LINK_SPI_DATA_TYPE data) { LINK_SPI_DATA_REG = data; }
LINK_SPI_DATA_TYPE getData() { return LINK_SPI_DATA_REG; }
void enableTransfer() { _setSOLow(); }
void disableTransfer() { _setSOHigh(); }

View File

@@ -54,6 +54,9 @@
// Max players. Default = 4 (LinkCable's limit), but can be increased to 5
#define LINK_UNIVERSAL_MAX_PLAYERS LINK_CABLE_MAX_PLAYERS
// Game ID Filter. Default = 0 (no filter)
#define LINK_UNIVERSAL_GAME_ID_FILTER 0
#define LINK_UNIVERSAL_DISCONNECTED LINK_CABLE_DISCONNECTED
#define LINK_UNIVERSAL_NO_DATA LINK_CABLE_NO_DATA
#define LINK_UNIVERSAL_MAX_ROOM_NUMBER 32000
@@ -64,7 +67,7 @@
#define LINK_UNIVERSAL_SERVE_WAIT_FRAMES 60
#define LINK_UNIVERSAL_SERVE_WAIT_FRAMES_RANDOM 30
static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v6.1.1";
static volatile char LINK_UNIVERSAL_VERSION[] = "LinkUniversal/v6.2.0";
void LINK_UNIVERSAL_ISR_VBLANK();
void LINK_UNIVERSAL_ISR_SERIAL();
@@ -393,7 +396,9 @@ class LinkUniversal {
break;
if (!server.isFull() &&
std::strcmp(server.gameName, config.gameName) == 0) {
std::strcmp(server.gameName, config.gameName) == 0 &&
(LINK_UNIVERSAL_GAME_ID_FILTER == 0 ||
server.gameId == LINK_UNIVERSAL_GAME_ID_FILTER)) {
u32 randomNumber = safeStoi(server.userName);
if (randomNumber > maxRandomNumber &&
randomNumber < LINK_UNIVERSAL_MAX_ROOM_NUMBER) {
@@ -417,7 +422,10 @@ class LinkUniversal {
char randomNumberStr[6];
std::snprintf(randomNumberStr, sizeof(randomNumberStr), "%d",
randomNumber);
if (!linkWireless->serve(config.gameName, randomNumberStr))
if (!linkWireless->serve(config.gameName, randomNumberStr,
LINK_UNIVERSAL_GAME_ID_FILTER > 0
? LINK_UNIVERSAL_GAME_ID_FILTER
: LINK_WIRELESS_MAX_GAME_ID))
return false;
}

View File

@@ -139,7 +139,7 @@
if (!reset()) \
return false;
static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v6.1.1";
static volatile char LINK_WIRELESS_VERSION[] = "LinkWireless/v6.2.0";
void LINK_WIRELESS_ISR_VBLANK();
void LINK_WIRELESS_ISR_SERIAL();

View File

@@ -0,0 +1,743 @@
#ifndef LINK_WIRELESS_MULTIBOOT_H
#define LINK_WIRELESS_MULTIBOOT_H
#pragma GCC push_options
#pragma GCC optimize("O2")
// --------------------------------------------------------------------------
// A Wireless Multiboot tool to send small ROMs from a GBA to up to 4 slaves.
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkWirelessMultiboot* linkWirelessMultiboot =
// new LinkWirelessMultiboot();
// - 2) Send the ROM:
// LinkWirelessMultiboot::Result result = linkWirelessMultiboot->sendRom(
// romBytes, // for current ROM, use: ((const u8*)MEM_EWRAM)
// romLength, // in bytes
// Multiboot", // game name
// "Test", // user name
// 0xffff, // game id
// 2, // number of players
// [](LinkWirelessMultiboot::MultibootProgress progress) {
// // check progress.[state,connectedClients,percentage]
//
// u16 keys = ~REG_KEYS & KEY_ANY;
// return keys & KEY_START;
// // (when this returns true, the transfer will be canceled)
// }
// );
// // `result` should be LinkWirelessMultiboot::Result::SUCCESS
// --------------------------------------------------------------------------
#include <tonc_core.h>
#include "LinkRawWireless.hpp"
#include "LinkWirelessOpenSDK.hpp"
// Enable logging (set `linkWirelessMultiboot->logger` and uncomment to enable)
// #define LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING
#ifdef LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING
#include <string>
#define LWMLOG(str) logger(str)
#else
#define LWMLOG(str)
#endif
#define LINK_WIRELESS_MULTIBOOT_MIN_ROM_SIZE (0x100 + 0xc0)
#define LINK_WIRELESS_MULTIBOOT_MAX_ROM_SIZE (256 * 1024)
#define LINK_WIRELESS_MULTIBOOT_MIN_PLAYERS 2
#define LINK_WIRELESS_MULTIBOOT_MAX_PLAYERS 5
#define LINK_WIRELESS_MULTIBOOT_HEADER_SIZE 0xC0
#define LINK_WIRELESS_MULTIBOOT_SETUP_MAGIC 0x003c0000
#define LINK_WIRELESS_MULTIBOOT_SETUP_TX 1
#define LINK_WIRELESS_MULTIBOOT_SETUP_WAIT_TIMEOUT 32
#define LINK_WIRELESS_MULTIBOOT_GAME_ID_MULTIBOOT_FLAG (1 << 15)
#define LINK_WIRELESS_MULTIBOOT_FRAME_LINES 228
#define LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS 4
#define LINK_WIRELESS_MULTIBOOT_TRY(CALL) \
if ((lastResult = CALL) != SUCCESS) { \
return finish(lastResult); \
}
const u8 LINK_WIRELESS_MULTIBOOT_CMD_START[] = {0x00, 0x54, 0x00, 0x00,
0x00, 0x02, 0x00};
const u8 LINK_WIRELESS_MULTIBOOT_CMD_START_SIZE = 7;
const u8 LINK_WIRELESS_MULTIBOOT_BOOTLOADER_HANDSHAKE[][6] = {
{0x00, 0x00, 0x52, 0x46, 0x55, 0x2d},
{0x4d, 0x42, 0x2d, 0x44, 0x4c, 0x00}};
const u8 LINK_WIRELESS_MULTIBOOT_BOOTLOADER_HANDSHAKE_SIZE = 6;
const u8 LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH[] = {
0x52, 0x46, 0x55, 0x2d, 0x4d, 0x42, 0x4f, 0x4f, 0x54, 0x00, 0x00, 0x00};
const u8 LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_OFFSET = 4;
const u8 LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_SIZE = 12;
static volatile char LINK_WIRELESS_MULTIBOOT_VERSION[] =
"LinkWirelessMultiboot/v6.2.0";
class LinkWirelessMultiboot {
public:
#ifdef LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING
typedef void (*Logger)(std::string);
Logger logger = [](std::string str) {};
#endif
enum Result {
SUCCESS,
INVALID_SIZE,
INVALID_PLAYERS,
CANCELED,
ADAPTER_NOT_DETECTED,
BAD_HANDSHAKE,
FAILURE
};
enum State { STOPPED, INITIALIZING, WAITING, PREPARING, SENDING, CONFIRMING };
struct MultibootProgress {
State state = STOPPED;
u32 connectedClients = 0;
u32 percentage = 0;
};
template <typename C>
Result sendRom(const u8* rom,
u32 romSize,
const char* gameName,
const char* userName,
const u16 gameId,
u8 players,
C cancel) {
if (romSize < LINK_WIRELESS_MULTIBOOT_MIN_ROM_SIZE)
return INVALID_SIZE;
if (romSize > LINK_WIRELESS_MULTIBOOT_MAX_ROM_SIZE)
return INVALID_SIZE;
if (players < LINK_WIRELESS_MULTIBOOT_MIN_PLAYERS ||
players > LINK_WIRELESS_MULTIBOOT_MAX_PLAYERS)
return INVALID_PLAYERS;
LWMLOG("starting...");
LINK_WIRELESS_MULTIBOOT_TRY(activate())
progress.state = INITIALIZING;
LINK_WIRELESS_MULTIBOOT_TRY(initialize(gameName, userName, gameId, players))
LWMLOG("waiting for connections...");
progress.state = WAITING;
LINK_WIRELESS_MULTIBOOT_TRY(waitForClients(players, cancel))
LWMLOG("all players are connected");
progress.state = PREPARING;
linkRawWireless->wait(LINK_WIRELESS_MULTIBOOT_FRAME_LINES);
LWMLOG("rom start command...");
LINK_WIRELESS_MULTIBOOT_TRY(sendRomStartCommand(cancel))
LWMLOG("SENDING ROM!");
progress.state = SENDING;
LINK_WIRELESS_MULTIBOOT_TRY(sendRomBytes(rom, romSize, cancel))
progress.state = CONFIRMING;
LINK_WIRELESS_MULTIBOOT_TRY(confirm(cancel))
LWMLOG("SUCCESS!");
return finish(SUCCESS);
}
~LinkWirelessMultiboot() {
delete linkRawWireless;
delete linkWirelessOpenSDK;
}
LinkRawWireless* linkRawWireless = new LinkRawWireless();
LinkWirelessOpenSDK* linkWirelessOpenSDK = new LinkWirelessOpenSDK();
private:
struct PendingTransfer {
u32 cursor;
bool ack;
bool isActive = false;
};
struct PendingTransferList {
std::array<PendingTransfer, LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS>
transfers;
PendingTransfer* max(bool ack = false) {
int maxCursor = -1;
int maxI = -1;
for (u32 i = 0; i < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS; i++) {
if (transfers[i].isActive && (int)transfers[i].cursor > maxCursor &&
(!ack || transfers[i].ack)) {
maxCursor = transfers[i].cursor;
maxI = i;
}
}
return maxI > -1 ? &transfers[maxI] : NULL;
}
PendingTransfer* minWithoutAck() {
u32 minCursor = 0xffffffff;
int minI = -1;
for (u32 i = 0; i < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS; i++) {
if (transfers[i].isActive && transfers[i].cursor < minCursor &&
!transfers[i].ack) {
minCursor = transfers[i].cursor;
minI = i;
}
}
return minI > -1 ? &transfers[minI] : NULL;
}
void addIfNeeded(u32 newCursor) {
auto maxTransfer = max();
if (maxTransfer != NULL && newCursor <= maxTransfer->cursor)
return;
for (u32 i = 0; i < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS; i++) {
if (!transfers[i].isActive) {
transfers[i].cursor = newCursor;
transfers[i].ack = false;
transfers[i].isActive = true;
break;
}
}
}
int ack(LinkWirelessOpenSDK::SequenceNumber sequence) {
int index = findIndex(sequence);
if (index == -1)
return -1;
transfers[index].ack = true;
auto maxAckTransfer = max(true);
bool canUpdateCursor =
maxAckTransfer != NULL && isAckCompleteUpTo(maxAckTransfer->cursor);
if (canUpdateCursor)
cleanup();
return canUpdateCursor ? maxAckTransfer->cursor + 1 : -1;
}
void cleanup() {
for (u32 i = 0; i < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS; i++) {
if (transfers[i].isActive && transfers[i].ack)
transfers[i].isActive = false;
}
}
bool isFull() {
return size() == LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS;
}
u32 size() {
u32 size = 0;
for (u32 i = 0; i < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS; i++)
if (transfers[i].isActive)
size++;
return size;
}
private:
bool isAckCompleteUpTo(u32 cursor) {
for (u32 i = 0; i < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS; i++)
if (transfers[i].isActive && !transfers[i].ack &&
transfers[i].cursor < cursor)
return false;
return true;
}
int findIndex(LinkWirelessOpenSDK::SequenceNumber sequence) {
for (u32 i = 0; i < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS; i++) {
if (transfers[i].isActive &&
LinkWirelessOpenSDK::SequenceNumber::fromPacketId(
transfers[i].cursor) == sequence) {
return i;
}
}
return -1;
}
};
struct Transfer {
u32 cursor = 0;
PendingTransferList pendingTransferList;
u32 nextCursor(bool canSendInflightPackets) {
u32 pendingCount = pendingTransferList.size();
if (canSendInflightPackets && pendingCount > 0 &&
pendingCount < LINK_WIRELESS_MULTIBOOT_MAX_INFLIGHT_PACKETS) {
return pendingTransferList.max()->cursor + 1;
} else {
auto minWithoutAck = pendingTransferList.minWithoutAck();
return minWithoutAck != NULL ? minWithoutAck->cursor : cursor;
}
}
void addIfNeeded(u32 newCursor) {
if (newCursor >= cursor)
pendingTransferList.addIfNeeded(newCursor);
}
u32 transferred() {
return cursor * LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER;
}
LinkWirelessOpenSDK::SequenceNumber sequence() {
return LinkWirelessOpenSDK::SequenceNumber::fromPacketId(cursor);
}
};
MultibootProgress progress;
Result lastResult;
LinkWirelessOpenSDK::ClientSDKHeader lastValidHeader;
__attribute__((noinline)) Result activate() {
if (!linkRawWireless->activate()) {
LWMLOG("! adapter not detected");
return ADAPTER_NOT_DETECTED;
}
LWMLOG("activated");
return SUCCESS;
}
__attribute__((noinline)) Result initialize(const char* gameName,
const char* userName,
const u16 gameId,
u8 players) {
if (!linkRawWireless->setup(players, LINK_WIRELESS_MULTIBOOT_SETUP_TX,
LINK_WIRELESS_MULTIBOOT_SETUP_WAIT_TIMEOUT,
LINK_WIRELESS_MULTIBOOT_SETUP_MAGIC)) {
LWMLOG("! setup failed");
return FAILURE;
}
LWMLOG("setup ok");
if (!linkRawWireless->broadcast(
gameName, userName,
gameId | LINK_WIRELESS_MULTIBOOT_GAME_ID_MULTIBOOT_FLAG)) {
LWMLOG("! broadcast failed");
return FAILURE;
}
LWMLOG("broadcast data set");
if (!linkRawWireless->startHost()) {
LWMLOG("! start host failed");
return FAILURE;
}
LWMLOG("host started");
return SUCCESS;
}
template <typename C>
__attribute__((noinline)) Result waitForClients(u8 players, C cancel) {
LinkRawWireless::AcceptConnectionsResponse acceptResponse;
u32 currentPlayers = 1;
while (linkRawWireless->playerCount() < players) {
if (cancel(progress))
return finish(CANCELED);
linkRawWireless->acceptConnections(acceptResponse);
if (linkRawWireless->playerCount() > currentPlayers) {
currentPlayers = linkRawWireless->playerCount();
progress.connectedClients = currentPlayers - 1;
u8 lastClientNumber =
acceptResponse
.connectedClients[acceptResponse.connectedClientsSize - 1]
.clientNumber;
LINK_WIRELESS_MULTIBOOT_TRY(handshakeClient(lastClientNumber, cancel))
}
}
return SUCCESS;
}
template <typename C>
Result handshakeClient(u8 clientNumber, C cancel) {
LinkWirelessOpenSDK::ClientPacket handshakePackets[2];
bool hasReceivedName = false;
LWMLOG("new client: " + std::to_string(clientNumber));
LINK_WIRELESS_MULTIBOOT_TRY(exchangeData(
clientNumber,
[this](LinkRawWireless::ReceiveDataResponse& response) {
return sendAndExpectData(toArray(), 0, 1, response);
},
[](LinkWirelessOpenSDK::ClientPacket packet) { return true; }, cancel))
// (initial client packet received)
LWMLOG("handshake (1/2)...");
LINK_WIRELESS_MULTIBOOT_TRY(exchangeACKData(
clientNumber,
[](LinkWirelessOpenSDK::ClientPacket packet) {
auto header = packet.header;
return header.n == 2 &&
header.commState == LinkWirelessOpenSDK::CommState::STARTING;
},
cancel))
// (n = 2, commState = 1)
LWMLOG("handshake (2/2)...");
LINK_WIRELESS_MULTIBOOT_TRY(exchangeACKData(
clientNumber,
[&handshakePackets](LinkWirelessOpenSDK::ClientPacket packet) {
auto header = packet.header;
bool isValid =
header.n == 1 && header.phase == 0 &&
header.commState == LinkWirelessOpenSDK::CommState::COMMUNICATING;
if (isValid)
handshakePackets[0] = packet;
return isValid;
},
cancel))
// (n = 1, commState = 2)
LWMLOG("receiving name...");
LINK_WIRELESS_MULTIBOOT_TRY(exchangeACKData(
clientNumber,
[this, &handshakePackets,
&hasReceivedName](LinkWirelessOpenSDK::ClientPacket packet) {
auto header = packet.header;
lastValidHeader = header;
if (header.n == 1 && header.phase == 1 &&
header.commState ==
LinkWirelessOpenSDK::CommState::COMMUNICATING) {
handshakePackets[1] = packet;
hasReceivedName = true;
}
return header.commState == LinkWirelessOpenSDK::CommState::OFF;
},
cancel))
// (commState = 0)
LWMLOG("validating name...");
for (u32 i = 0; i < 2; i++) {
auto receivedPayload = handshakePackets[i].payload;
auto expectedPayload = LINK_WIRELESS_MULTIBOOT_BOOTLOADER_HANDSHAKE[i];
for (u32 j = 0; j < LINK_WIRELESS_MULTIBOOT_BOOTLOADER_HANDSHAKE_SIZE;
j++) {
if (!hasReceivedName || receivedPayload[j] != expectedPayload[j]) {
LWMLOG("! bad payload");
return finish(BAD_HANDSHAKE);
}
}
}
LWMLOG("draining queue...");
bool hasFinished = false;
while (!hasFinished) {
if (cancel(progress))
return finish(CANCELED);
LinkRawWireless::ReceiveDataResponse response;
LINK_WIRELESS_MULTIBOOT_TRY(sendAndExpectData(toArray(), 0, 1, response))
auto childrenData = linkWirelessOpenSDK->getChildrenData(response);
hasFinished = childrenData.responses[clientNumber].packetsSize == 0;
}
// (no more client packets)
LWMLOG("client " + std::to_string(clientNumber) + " accepted");
return SUCCESS;
}
template <typename C>
__attribute__((noinline)) Result sendRomStartCommand(C cancel) {
for (u32 i = 0; i < progress.connectedClients; i++) {
LINK_WIRELESS_MULTIBOOT_TRY(exchangeNewData(
i,
linkWirelessOpenSDK->createServerBuffer(
LINK_WIRELESS_MULTIBOOT_CMD_START,
LINK_WIRELESS_MULTIBOOT_CMD_START_SIZE,
{1, 0, LinkWirelessOpenSDK::CommState::STARTING}, 1 << i),
cancel))
}
return SUCCESS;
}
template <typename C>
__attribute__((noinline)) Result sendRomBytes(const u8* rom,
u32 romSize,
C cancel) {
LinkWirelessOpenSDK::ChildrenData childrenData;
std::array<Transfer, LINK_WIRELESS_MULTIBOOT_MAX_PLAYERS - 1> transfers;
u8 firstPagePatch[LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER];
for (u32 i = 0; i < LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER; i++) {
firstPagePatch[i] =
i >= LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_OFFSET &&
i < LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_OFFSET +
LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_SIZE
? LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH
[i - LINK_WIRELESS_MULTIBOOT_ROM_HEADER_PATCH_OFFSET]
: rom[i];
}
progress.percentage = 0;
u32 minClient = 0;
while (transfers[minClient = findMinClient(transfers)].transferred() <
romSize) {
if (cancel(progress))
return finish(CANCELED);
u32 cursor = findMinCursor(transfers);
u32 offset = cursor * LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER;
auto sequence = LinkWirelessOpenSDK::SequenceNumber::fromPacketId(cursor);
const u8* bufferToSend = cursor == 0 ? (const u8*)firstPagePatch : rom;
auto sendBuffer = linkWirelessOpenSDK->createServerBuffer(
bufferToSend, romSize, sequence, 0b1111, offset);
for (u32 i = 0; i < progress.connectedClients; i++)
transfers[i].addIfNeeded(cursor);
LinkRawWireless::ReceiveDataResponse response;
LINK_WIRELESS_MULTIBOOT_TRY(sendAndExpectData(sendBuffer, response))
childrenData = linkWirelessOpenSDK->getChildrenData(response);
for (u32 i = 0; i < progress.connectedClients; i++) {
for (u32 j = 0; j < childrenData.responses[i].packetsSize; j++) {
auto header = childrenData.responses[i].packets[j].header;
if (header.isACK) {
int newAckCursor =
transfers[i].pendingTransferList.ack(header.sequence());
if (newAckCursor > -1)
transfers[i].cursor = newAckCursor;
}
}
}
u32 newPercentage =
min(transfers[findMinClient(transfers)].transferred() * 100 / romSize,
100);
if (newPercentage != progress.percentage) {
progress.percentage = newPercentage;
LWMLOG("-> " + std::to_string(newPercentage));
}
}
return SUCCESS;
}
__attribute__((noinline)) u32 findMinClient(
std::array<Transfer, LINK_WIRELESS_MULTIBOOT_MAX_PLAYERS - 1>&
transfers) {
u32 minTransferredBytes = 0xffffffff;
u32 minClient = 0;
for (u32 i = 0; i < progress.connectedClients; i++) {
u32 transferred = transfers[i].transferred();
if (transferred < minTransferredBytes) {
minTransferredBytes = transferred;
minClient = i;
}
}
return minClient;
}
__attribute__((noinline)) u32 findMinCursor(
std::array<Transfer, LINK_WIRELESS_MULTIBOOT_MAX_PLAYERS - 1>&
transfers) {
u32 minNextCursor = 0xffffffff;
bool canSendInflightPackets = true;
for (u32 i = 0; i < progress.connectedClients; i++) {
if (transfers[i].pendingTransferList.isFull())
canSendInflightPackets = false;
}
for (u32 i = 0; i < progress.connectedClients; i++) {
u32 nextCursor = transfers[i].nextCursor(canSendInflightPackets);
if (nextCursor < minNextCursor)
minNextCursor = nextCursor;
}
return minNextCursor;
}
template <typename C>
__attribute__((noinline)) Result confirm(C cancel) {
LWMLOG("confirming (1/2)...");
for (u32 i = 0; i < progress.connectedClients; i++) {
LINK_WIRELESS_MULTIBOOT_TRY(exchangeNewData(
i,
linkWirelessOpenSDK->createServerBuffer(
{}, 0, {0, 0, LinkWirelessOpenSDK::CommState::ENDING}, 1 << i),
cancel))
}
LWMLOG("confirming (2/2)...");
for (u32 i = 0; i < progress.connectedClients; i++) {
LinkRawWireless::ReceiveDataResponse response;
LINK_WIRELESS_MULTIBOOT_TRY(sendAndExpectData(
linkWirelessOpenSDK->createServerBuffer(
{}, 0, {1, 0, LinkWirelessOpenSDK::CommState::OFF}, 1 << i),
response))
}
return SUCCESS;
}
template <typename C>
__attribute__((noinline)) Result exchangeNewData(
u8 clientNumber,
LinkWirelessOpenSDK::SendBuffer<LinkWirelessOpenSDK::ServerSDKHeader>
sendBuffer,
C cancel) {
LINK_WIRELESS_MULTIBOOT_TRY(exchangeData(
clientNumber,
[this, &sendBuffer](LinkRawWireless::ReceiveDataResponse& response) {
return sendAndExpectData(sendBuffer, response);
},
[&sendBuffer](LinkWirelessOpenSDK::ClientPacket packet) {
auto header = packet.header;
return header.isACK == 1 &&
header.sequence() == sendBuffer.header.sequence();
},
cancel))
return SUCCESS;
}
template <typename V, typename C>
__attribute__((noinline)) Result exchangeACKData(u8 clientNumber,
V validatePacket,
C cancel) {
LINK_WIRELESS_MULTIBOOT_TRY(exchangeData(
clientNumber,
[this, clientNumber](LinkRawWireless::ReceiveDataResponse& response) {
return sendAndExpectData(linkWirelessOpenSDK->createServerACKBuffer(
lastValidHeader, clientNumber),
response);
},
validatePacket, cancel))
return SUCCESS;
}
template <typename F, typename V, typename C>
__attribute__((noinline)) Result exchangeData(u8 clientNumber,
F sendAction,
V validatePacket,
C cancel) {
bool hasFinished = false;
while (!hasFinished) {
if (cancel(progress))
return finish(CANCELED);
LinkRawWireless::ReceiveDataResponse response;
LINK_WIRELESS_MULTIBOOT_TRY(sendAction(response))
auto childrenData = linkWirelessOpenSDK->getChildrenData(response);
for (u32 i = 0; i < childrenData.responses[clientNumber].packetsSize;
i++) {
auto packet = childrenData.responses[clientNumber].packets[i];
auto header = packet.header;
if (validatePacket(packet)) {
hasFinished = true;
lastValidHeader = header;
break;
}
}
}
return SUCCESS;
}
__attribute__((noinline)) Result sendAndExpectData(
LinkWirelessOpenSDK::SendBuffer<LinkWirelessOpenSDK::ServerSDKHeader>
sendBuffer,
LinkRawWireless::ReceiveDataResponse& response) {
return sendAndExpectData(sendBuffer.data, sendBuffer.dataSize,
sendBuffer.totalByteCount, response);
}
__attribute__((noinline)) Result sendAndExpectData(
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> data,
u32 dataSize,
u32 _bytes,
LinkRawWireless::ReceiveDataResponse& response) {
LinkRawWireless::RemoteCommand remoteCommand;
bool success = false;
success =
linkRawWireless->sendDataAndWait(data, dataSize, remoteCommand, _bytes);
if (!success) {
LWMLOG("! sendDataAndWait failed");
return FAILURE;
}
if (remoteCommand.commandId != 0x28) {
LWMLOG("! expected EVENT 0x28");
LWMLOG("! but got " + toHex(remoteCommand.commandId));
return FAILURE;
}
if (remoteCommand.paramsSize > 0) {
u8 expectedActiveChildren = 0;
for (u32 i = 0; i < progress.connectedClients; i++)
expectedActiveChildren |= 1 << i;
u8 activeChildren =
(remoteCommand.params[0] >> 8) & expectedActiveChildren;
if (activeChildren != expectedActiveChildren) {
LWMLOG("! client timeout [" + std::to_string(activeChildren) + "]");
LWMLOG("! vs expected: [" + std::to_string(expectedActiveChildren) +
"]");
return FAILURE;
}
}
success = linkRawWireless->receiveData(response);
if (!success) {
LWMLOG("! receiveData failed");
return FAILURE;
}
return SUCCESS;
}
__attribute__((noinline)) Result finish(Result result) {
linkRawWireless->deactivate();
progress.state = STOPPED;
progress.connectedClients = 0;
progress.percentage = 0;
return result;
}
#ifdef LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING
template <typename I>
std::string toHex(I w, size_t hex_len = sizeof(I) << 1) {
static const char* digits = "0123456789ABCDEF";
std::string rc(hex_len, '0');
for (size_t i = 0, j = (hex_len - 1) * 4; i < hex_len; ++i, j -= 4)
rc[i] = digits[(w >> j) & 0x0f];
return rc;
}
#endif
template <typename... Args>
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> toArray(
Args... args) {
return {static_cast<u32>(args)...};
}
};
extern LinkWirelessMultiboot* linkWirelessMultiboot;
#undef LWMLOG
#pragma GCC pop_options
#endif // LINK_WIRELESS_MULTIBOOT_H

361
lib/LinkWirelessOpenSDK.hpp Normal file
View File

@@ -0,0 +1,361 @@
#ifndef LINK_WIRELESS_OPEN_SDK_H
#define LINK_WIRELESS_OPEN_SDK_H
// --------------------------------------------------------------------------
// An open-source implementation of the "official" Wireless Adapter protocol.
// --------------------------------------------------------------------------
// - Check out README.md for documentation.
// --------------------------------------------------------------------------
#include <tonc_core.h>
#include <tonc_math.h>
#include "LinkRawWireless.hpp"
#define LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_WORDS 23
#define LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_BYTES_SERVER 87
#define LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_BYTES_CLIENT 16
#define LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER 3
#define LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT 2
#define LINK_WIRELESS_OPEN_SDK_HEADER_MASK_SERVER \
((1 << (LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER * 8)) - 1)
#define LINK_WIRELESS_OPEN_SDK_HEADER_MASK_CLIENT \
((1 << (LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT * 8)) - 1)
#define LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER \
(LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_BYTES_SERVER - \
LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER)
#define LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_CLIENT \
(LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_BYTES_CLIENT - \
LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT)
#define LINK_WIRELESS_OPEN_SDK_MAX_PACKETS_SERVER \
(LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_BYTES_SERVER / \
LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER)
#define LINK_WIRELESS_OPEN_SDK_MAX_PACKETS_CLIENT \
(LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_BYTES_CLIENT / \
LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT)
static volatile char LINK_WIRELESS_OPEN_SDK_VERSION[] =
"LinkWirelessOpenSDK/v6.2.0";
class LinkWirelessOpenSDK {
public:
template <class T>
struct SendBuffer {
T header;
std::array<u32, LINK_WIRELESS_OPEN_SDK_MAX_TRANSFER_WORDS> data;
u32 dataSize = 0;
u32 totalByteCount = 0;
};
enum CommState : unsigned int {
OFF = 0,
STARTING = 1,
COMMUNICATING = 2,
ENDING = 3,
DIRECT = 4
};
struct SequenceNumber {
u32 n = 0;
u32 phase = 0;
CommState commState = OFF;
static SequenceNumber fromPacketId(u32 packetId) {
return SequenceNumber{.n = ((packetId + 4) / 4) % 4,
packetId % 4,
.commState = COMMUNICATING};
}
bool operator==(const SequenceNumber& other) {
return n == other.n && phase == other.phase &&
commState == other.commState;
}
};
struct ServerSDKHeader {
unsigned int payloadSize : 7;
unsigned int _unused_ : 2;
unsigned int phase : 2;
unsigned int n : 2;
unsigned int isACK : 1;
CommState commState : 4;
unsigned int targetSlots : 4;
SequenceNumber sequence() {
return SequenceNumber{.n = n, .phase = phase, .commState = commState};
}
};
union ServerSDKHeaderSerializer {
ServerSDKHeader asStruct;
u32 asInt;
};
struct ServerPacket {
ServerSDKHeader header;
u8 payload[LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER];
};
struct ServerResponse {
ServerPacket packets[LINK_WIRELESS_OPEN_SDK_MAX_PACKETS_SERVER];
u32 packetsSize = 0;
};
struct ParentData {
ServerResponse response;
};
struct ClientSDKHeader {
unsigned int payloadSize : 5;
unsigned int phase : 2;
unsigned int n : 2;
unsigned int isACK : 1;
CommState commState : 4;
SequenceNumber sequence() {
return SequenceNumber{.n = n, .phase = phase, .commState = commState};
}
};
union ClientSDKHeaderSerializer {
ClientSDKHeader asStruct;
u16 asInt;
};
struct ClientPacket {
ClientSDKHeader header;
u8 payload[LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_CLIENT];
};
struct ClientResponse {
ClientPacket packets[LINK_WIRELESS_OPEN_SDK_MAX_PACKETS_CLIENT];
u32 packetsSize = 0;
};
struct ChildrenData {
ClientResponse responses[4];
};
ChildrenData getChildrenData(LinkRawWireless::ReceiveDataResponse response) {
u8* buffer = (u8*)response.data;
u32 cursor = 0;
ChildrenData childrenData;
if (response.sentBytes[1] + response.sentBytes[2] + response.sentBytes[3] +
response.sentBytes[4] >
response.dataSize * 4)
return childrenData;
for (u32 i = 1; i < LINK_RAW_WIRELESS_MAX_PLAYERS; i++) {
ClientResponse* clientResponse = &childrenData.responses[i - 1];
u32 remainingBytes = response.sentBytes[i];
while (remainingBytes >= LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT) {
ClientPacket* packet =
&clientResponse->packets[clientResponse->packetsSize];
u32 headerInt = *((u16*)(buffer + cursor));
packet->header = parseClientHeader(headerInt);
cursor += LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT;
remainingBytes -= LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT;
if (packet->header.payloadSize > 0 &&
packet->header.payloadSize <=
LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_CLIENT &&
remainingBytes >= packet->header.payloadSize) {
for (u32 j = 0; j < packet->header.payloadSize; j++)
packet->payload[j] = buffer[cursor++];
remainingBytes -= packet->header.payloadSize;
}
clientResponse->packetsSize++;
}
}
return childrenData;
}
ParentData getParentData(LinkRawWireless::ReceiveDataResponse response) {
u8* buffer = (u8*)response.data;
u32 cursor = 0;
ParentData parentData;
if (response.sentBytes[0] > response.dataSize * 4)
return parentData;
ServerResponse* serverResponse = &parentData.response;
u32 remainingBytes = response.sentBytes[0];
while (remainingBytes >= LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER) {
ServerPacket* packet =
&serverResponse->packets[serverResponse->packetsSize];
u32 headerInt = (*((u16*)(buffer + cursor))) |
(((*((u8*)(buffer + cursor + 2)))) << 16);
packet->header = parseServerHeader(headerInt);
cursor += LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER;
remainingBytes -= LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER;
if (packet->header.payloadSize > 0 &&
packet->header.payloadSize <=
LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER &&
remainingBytes >= packet->header.payloadSize) {
for (u32 j = 0; j < packet->header.payloadSize; j++)
packet->payload[j] = buffer[cursor++];
remainingBytes -= packet->header.payloadSize;
}
serverResponse->packetsSize++;
}
return parentData;
}
SendBuffer<ServerSDKHeader> createServerBuffer(const u8* fullPayload,
u32 fullPayloadSize,
SequenceNumber sequence,
u8 targetSlots = 0b1111,
u32 offset = 0) {
SendBuffer<ServerSDKHeader> buffer;
u32 payloadSize =
min(fullPayloadSize, LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER);
buffer.header.isACK = 0;
buffer.header.targetSlots = targetSlots;
buffer.header.payloadSize = payloadSize;
buffer.header.n = sequence.n;
buffer.header.phase = sequence.phase;
buffer.header.commState = sequence.commState;
u32 headerInt = serializeServerHeader(buffer.header);
buffer.data[buffer.dataSize++] = headerInt;
if (offset < fullPayloadSize)
buffer.data[0] |= fullPayload[offset] << 24;
for (u32 i = 1; i < payloadSize; i += 4) {
u32 word = 0;
for (u32 j = 0; j < 4; j++) {
if (offset + i + j < fullPayloadSize &&
i + j < LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_SERVER) {
u8 byte = fullPayload[offset + i + j];
word |= byte << (j * 8);
}
}
buffer.data[buffer.dataSize++] = word;
}
buffer.totalByteCount =
LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER + payloadSize;
return buffer;
}
SendBuffer<ServerSDKHeader> createServerACKBuffer(
ClientSDKHeader clientHeader,
u8 clientNumber) {
SendBuffer<ServerSDKHeader> buffer;
buffer.header = createACKHeaderFor(clientHeader, clientNumber);
u32 headerInt = serializeServerHeader(buffer.header);
buffer.data[buffer.dataSize++] = headerInt;
buffer.totalByteCount = LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_SERVER;
return buffer;
}
SendBuffer<ClientSDKHeader> createClientBuffer(const u8* fullPayload,
u32 fullPayloadSize,
SequenceNumber sequence,
u32 offset = 0) {
SendBuffer<ClientSDKHeader> buffer;
u32 payloadSize =
min(fullPayloadSize, LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_CLIENT);
buffer.header.isACK = 0;
buffer.header.payloadSize = payloadSize;
buffer.header.n = sequence.n;
buffer.header.phase = sequence.phase;
buffer.header.commState = sequence.commState;
u16 headerInt = serializeClientHeader(buffer.header);
buffer.data[buffer.dataSize++] = headerInt;
if (offset < fullPayloadSize)
buffer.data[0] |= fullPayload[offset] << 16;
if (offset + 1 < fullPayloadSize)
buffer.data[0] |= fullPayload[offset + 1] << 24;
for (u32 i = 2; i < payloadSize; i += 4) {
u32 word = 0;
for (u32 j = 0; j < 4; j++) {
if (offset + i + j < fullPayloadSize &&
i + j < LINK_WIRELESS_OPEN_SDK_MAX_PAYLOAD_CLIENT) {
u8 byte = fullPayload[offset + i + j];
word |= byte << (j * 8);
}
}
buffer.data[buffer.dataSize++] = word;
}
buffer.totalByteCount =
LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT + payloadSize;
return buffer;
}
SendBuffer<ClientSDKHeader> createClientACKBuffer(
ServerSDKHeader serverHeader) {
SendBuffer<ClientSDKHeader> buffer;
buffer.header = createACKHeaderFor(serverHeader);
u16 headerInt = serializeClientHeader(buffer.header);
buffer.data[buffer.dataSize++] = headerInt;
buffer.totalByteCount = LINK_WIRELESS_OPEN_SDK_HEADER_SIZE_CLIENT;
return buffer;
}
ServerSDKHeader createACKHeaderFor(ClientSDKHeader clientHeader,
u8 clientNumber) {
ServerSDKHeader serverHeader;
serverHeader.isACK = 1;
serverHeader.targetSlots = (1 << clientNumber);
serverHeader.payloadSize = 0;
serverHeader.n = clientHeader.n;
serverHeader.phase = clientHeader.phase;
serverHeader.commState = clientHeader.commState;
return serverHeader;
}
ClientSDKHeader createACKHeaderFor(ServerSDKHeader serverHeader) {
ClientSDKHeader clientHeader;
clientHeader.isACK = 1;
clientHeader.payloadSize = 0;
clientHeader.n = serverHeader.n;
clientHeader.phase = serverHeader.phase;
clientHeader.commState = serverHeader.commState;
return clientHeader;
}
ClientSDKHeader parseClientHeader(u32 clientHeaderInt) {
ClientSDKHeaderSerializer clientSerializer;
clientSerializer.asInt =
clientHeaderInt & LINK_WIRELESS_OPEN_SDK_HEADER_MASK_CLIENT;
return clientSerializer.asStruct;
}
u16 serializeClientHeader(ClientSDKHeader clientHeader) {
ClientSDKHeaderSerializer clientSerializer;
clientSerializer.asStruct = clientHeader;
return clientSerializer.asInt & LINK_WIRELESS_OPEN_SDK_HEADER_MASK_CLIENT;
}
ServerSDKHeader parseServerHeader(u32 serverHeaderInt) {
ServerSDKHeaderSerializer serverSerializer;
serverSerializer.asInt =
serverHeaderInt & LINK_WIRELESS_OPEN_SDK_HEADER_MASK_SERVER;
return serverSerializer.asStruct;
}
u32 serializeServerHeader(ServerSDKHeader serverHeader) {
ServerSDKHeaderSerializer serverSerializer;
serverSerializer.asStruct = serverHeader;
return serverSerializer.asInt & LINK_WIRELESS_OPEN_SDK_HEADER_MASK_SERVER;
}
};
#endif // LINK_WIRELESS_OPEN_SDK_H