Merge pull request #22 from afska/v7.0.0
🟪 LinkCube, 📱 LinkMobile, ⚙️ lots of improvements
23
.editorconfig
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
||||
# Change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
9
.gitignore
vendored
|
|
@ -1,12 +1,15 @@
|
|||
# Directories
|
||||
build
|
||||
.vscode
|
||||
build/
|
||||
.vscode/
|
||||
node_modules/
|
||||
|
||||
# Files
|
||||
examples/multiboot
|
||||
.DS_Store
|
||||
*.elf
|
||||
*.gba
|
||||
*.sav
|
||||
*.sa1
|
||||
*.sa2
|
||||
*.sa3
|
||||
*.sa4
|
||||
*.sa4
|
||||
|
|
|
|||
17
.licenses/gba-hpp.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
gba-hpp is available under the [zlib license](https://www.zlib.net/zlib_license.html) :
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
276
README.md
|
|
@ -1,6 +1,6 @@
|
|||
# gba-link-connection
|
||||
|
||||
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.
|
||||
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. [C bindings](#c-bindings) are also included for compatibility.
|
||||
|
||||
- [👾](#-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!
|
||||
|
|
@ -13,6 +13,8 @@ A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port.
|
|||
- [🔌](#-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!
|
||||
- [⏱️](#%EF%B8%8F-LinkUART) [LinkUART.hpp](lib/LinkUART.hpp): Easily connect to **any PC** using a USB to UART cable!
|
||||
- [🟪](#-LinkCube) [LinkCube.hpp](lib/LinkCube.hpp): Exchange data with a *Wii* or a *GameCube* using the classic **Joybus** protocol!
|
||||
- [📱](#-LinkMobile) [LinkMobile.hpp](lib/LinkMobile.hpp): Connect to **the internet** using the *Mobile Adapter GB*, brought back to life thanks to the [REON](https://github.com/REONTeam) project!
|
||||
- [🖱️](#%EF%B8%8F-LinkPS2Mouse) [LinkPS2Mouse.hpp](lib/LinkPS2Mouse.hpp): Connect a **PS/2 mouse** to the GBA for extended controls!
|
||||
- [⌨️](#%EF%B8%8F-LinkPS2Keyboard) [LinkPS2Keyboard.hpp](lib/LinkPS2Keyboard.hpp): Connect a **PS/2 keyboard** to the GBA for extended controls!
|
||||
|
||||
|
|
@ -24,20 +26,55 @@ A set of Game Boy Advance (GBA) C++ libraries to interact with the Serial Port.
|
|||
|
||||
## Usage
|
||||
|
||||
- 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.
|
||||
* For `LinkCable`/`LinkWireless`/`LinkUniversal` there are stress tests that you can use to tweak your configuration.
|
||||
- Copy the contents of the [lib/](lib/) folder into a directory that is part of your project's include path. Then, `#include` the library you need, such as [LinkCable.hpp](lib/LinkCable.hpp), in your project. No external dependencies are required.
|
||||
- For initial instructions and setup details, refer to the large comment block at the beginning of each file, the documentation included here, and the provided examples.
|
||||
- Check out the [examples/](examples/) folder.
|
||||
* **Compiled ROMs are available** in [Releases](https://github.com/afska/gba-link-connection/releases).
|
||||
* The example code uses [libtonc](https://github.com/gbadev-org/libtonc) (and [libugba](https://github.com/AntonioND/libugba) for interrupts), but any library can be used.
|
||||
* The examples can be tested on real GBAs or using emulators.
|
||||
* For `LinkCable`/`LinkWireless`/`LinkUniversal`, stress tests are provided to help you tweak your configuration. The [LinkUniversal_real](https://github.com/afska/gba-link-universal-test) ROM tests a more real scenario using an audio player, a background video, text and sprites.
|
||||
* The `LinkCableMultiboot_demo` and `LinkWirelessMultiboot_demo` examples can bootstrap all other examples, allowing you to test with multiple units even if you only have one flashcart.
|
||||
|
||||
To learn implementation details, you might also want to check out the [docs](docs) folder, which contains important documentation.
|
||||
> The files use some compiler extensions, so using **GCC** is required.
|
||||
|
||||
### Makefile actions (for all examples)
|
||||
> The example ROMs were compiled with [devkitPro](https://devkitpro.org), using GCC `14.1.0` with `-std=c++17` as the standard and `-Ofast` as the optimization level.
|
||||
|
||||
> To learn implementation details, you might also want to check out the [docs/](docs/) folder, which contains important documentation.
|
||||
|
||||
### Compiling the examples
|
||||
|
||||
Running `./compile.sh` builds all the examples with the right configuration.
|
||||
|
||||
The project must be in a path without spaces; devkitARM and some *nix commands are required.
|
||||
|
||||
All the projects understand these Makefile actions:
|
||||
|
||||
```bash
|
||||
make [ clean | build | start | rebuild | restart ]
|
||||
```
|
||||
|
||||
### C bindings
|
||||
|
||||
- To use the libraries in a C project, include the files from the [lib/c_bindings/](lib/c_bindings/) directory.
|
||||
- For documentation, use this `README.md` file or comments inside the main C++ files.
|
||||
- Some libraries may not be available in C.
|
||||
- Some methods/overloads may not be available in the C implementations.
|
||||
- Unlike the main libraries, C bindings depend on *libtonc*.
|
||||
|
||||
```cpp
|
||||
// Instantiating
|
||||
LinkSomething* linkSomething = new LinkSomething(a, b); // C++
|
||||
LinkSomethingHandle cLinkSomething = C_LinkSomething_create(a, b); // C
|
||||
|
||||
// Calling methods
|
||||
linkSomething->method(a, b); // C++
|
||||
C_LinkSomething_method(cLinkSomething, a, b); // C
|
||||
|
||||
// Destroying
|
||||
delete linkSomething; // C++
|
||||
C_LinkSomething_destroy(cLinkSomething); // C
|
||||
```
|
||||
|
||||
# 👾 LinkCable
|
||||
|
||||
*(aka Multi-Play Mode)*
|
||||
|
|
@ -55,9 +92,8 @@ The library uses message queues to send/receive data and transmits when it's pos
|
|||
Name | Type | Default | Description
|
||||
--- | --- | --- | ---
|
||||
`baudRate` | **BaudRate** | `BAUD_RATE_1` | Sets a specific baud rate.
|
||||
`timeout` | **u32** | `3` | Number of *frames* without an `II_SERIAL` IRQ to reset the connection.
|
||||
`remoteTimeout` | **u32** | `5` | Number of *messages* with `0xFFFF` to mark a player as disconnected.
|
||||
`interval` | **u16** | `50` | Number of *1024-cycle ticks* (61.04μs) between transfers *(50 = 3.052ms)*. It's the interval of Timer #`sendTimerId`. Lower values will transfer faster but also consume more CPU.
|
||||
`timeout` | **u32** | `3` | Maximum number of *frames* without receiving data from other player before marking them as disconnected or resetting the connection.
|
||||
`interval` | **u16** | `50` | Number of *1024-cycle ticks* (61.04μs) between transfers *(50 = 3.052ms)*. It's the interval of Timer #`sendTimerId`. Lower values will transfer faster but also consume more CPU. You can use `Link::perFrame(...)` to convert from *packets per frame* to *interval values*.
|
||||
`sendTimerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending.
|
||||
|
||||
You can update these values at any time without creating a new instance:
|
||||
|
|
@ -67,6 +103,9 @@ You can update these values at any time without creating a new instance:
|
|||
|
||||
You can also change these compile-time constants:
|
||||
- `LINK_CABLE_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max **per player**). The default value is `15`, which seems fine for most games.
|
||||
- This affects how much memory is allocated. With the default value, it's around `390` bytes. There's a double-buffered pending queue (to avoid data races), `1` incoming queue and `1` outgoing queue.
|
||||
- You can approximate the memory usage with:
|
||||
- `(LINK_CABLE_QUEUE_SIZE * sizeof(u16) * LINK_CABLE_MAX_PLAYERS) * 3 + LINK_CABLE_QUEUE_SIZE * sizeof(u16)` <=> `LINK_CABLE_QUEUE_SIZE * 26`
|
||||
|
||||
## Methods
|
||||
|
||||
|
|
@ -77,13 +116,13 @@ Name | Return type | Description
|
|||
`deactivate()` | - | Deactivates the library.
|
||||
`isConnected()` | **bool** | Returns `true` if there are at least 2 connected players.
|
||||
`playerCount()` | **u8** *(0~4)* | Returns the number of connected players.
|
||||
`currentPlayerId()` | **u8** *(0~3)* | Returns the current player id.
|
||||
`currentPlayerId()` | **u8** *(0~3)* | Returns the current player ID.
|
||||
`sync()` | - | Call this method every time you need to fetch new data.
|
||||
`waitFor(playerId)` | **bool** | Waits for data from player #`playerId`. Returns `true` on success, or `false` on disconnection.
|
||||
`waitFor(playerId, cancel)` | **bool** | Like `waitFor(playerId)` but accepts a `cancel()` function. The library will continuously invoke it, and abort the wait if it returns `true`.
|
||||
`canRead(playerId)` | **bool** | Returns `true` if there are pending messages from player #`playerId`. Keep in mind that if this returns `false`, it will keep doing so until you *fetch new data* with `sync()`.
|
||||
`read(playerId)` | **u16** | Dequeues and returns the next message from player #`playerId`.
|
||||
`peek(playerId)` | **u16** | Returns the next message from player #`playerId` without dequeuing it.
|
||||
`read(playerId)` | **u16** | Dequeues and returns the next message from player #`playerId`. If there's no data from that player, a `0` will be returned.
|
||||
`peek(playerId)` | **u16** | Returns the next message from player #`playerId` without dequeuing it. If there's no data from that player, a `0` will be returned.
|
||||
`send(data)` | - | Sends `data` to all connected players.
|
||||
|
||||
⚠️ `0xFFFF` and `0x0` are reserved values, so don't send them!
|
||||
|
|
@ -94,15 +133,22 @@ 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.
|
||||
|
||||
Its demo (`LinkCableMultiboot_demo`) has all the other gba-link-connection ROMs bundled with it, so it can be used to quickly test the library.
|
||||
|
||||

|
||||
|
||||
You can change these compile-time constants:
|
||||
- `LINK_CABLE_MULTIBOOT_PALETTE_DATA`: to control how the logo is displayed.
|
||||
- Format: `0b1CCCDSS1`, where `C`=color, `D`=direction, `S`=speed.
|
||||
- Default: `0b10010011`.
|
||||
|
||||
## Methods
|
||||
|
||||
Name | Return type | Description
|
||||
--- | --- | ---
|
||||
`sendRom(rom, romSize, cancel)` | **LinkCableMultiboot::Result** | Sends the `rom`. During the handshake process, the library will continuously invoke `cancel`, and abort the transfer if it returns `true`. The `romSize` must be a number between `448` and `262144`, and a multiple of `16`. Once completed, the return value should be `LinkCableMultiboot::Result::SUCCESS`.
|
||||
`sendRom(rom, romSize, cancel, [mode])` | **LinkCableMultiboot::Result** | Sends the `rom`. During the handshake process, the library will continuously invoke `cancel`, and abort the transfer if it returns `true`. The `romSize` must be a number between `448` and `262144`, and a multiple of `16`. The `mode` can be either `LinkCableMultiboot::TransferMode::MULTI_PLAY` for GBA cable (default value) or `LinkCableMultiboot::TransferMode::SPI` for GBC cable. Once completed, the return value should be `LinkCableMultiboot::Result::SUCCESS`.
|
||||
|
||||
⚠️ for better results, turn on the GBAs **after** calling the `sendRom` method!
|
||||
⚠️ stop DMA before sending the ROM! _(you might need to stop your audio player)_
|
||||
|
||||
# 🔧👾 LinkRawCable
|
||||
|
||||
|
|
@ -119,14 +165,14 @@ 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`).
|
||||
`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)` | **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`.
|
||||
`getAsyncData()` | **LinkRawCable::Response** | If the async state is `READY`, returns the remote data and switches the state back to `IDLE`. If not, returns an empty response.
|
||||
`getBaudRate()` | **LinkRawCable::BaudRate** | Returns the current `baudRate`.
|
||||
`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`.
|
||||
|
||||
- don't send `0xFFFF`, it's a reserved value that means *disconnected client*
|
||||
- only `transfer(...)` if `isReady()`
|
||||
|
|
@ -149,12 +195,10 @@ Name | Type | Default | Description
|
|||
--- | --- | --- | ---
|
||||
`forwarding` | **bool** | `true` | If `true`, the server forwards all messages to the clients. Otherwise, clients only see messages sent from the server (ignoring other peers).
|
||||
`retransmission` | **bool** | `true` | If `true`, the library handles retransmission for you, so there should be no packet loss.
|
||||
`maxPlayers` | **u8** *(2~5)* | `5` | Maximum number of allowed players. The adapter will accept connections after reaching the limit, but the library will ignore them. If your game only supports -for example- two players, set this to `2` as it will make transfers faster.
|
||||
`timeout` | **u32** | `10` | Number of *frames* without receiving *any* data to reset the connection.
|
||||
`remoteTimeout` | **u32** | `10` | Number of *successful transfers* without a message from a client to mark the player as disconnected.
|
||||
`interval` | **u16** | `50` | Number of *1024-cycle ticks* (61.04μs) between transfers *(50 = 3.052ms)*. It's the interval of Timer #`sendTimerId`. Lower values will transfer faster but also consume more CPU.
|
||||
`maxPlayers` | **u8** *(2~5)* | `5` | Maximum number of allowed players.
|
||||
`timeout` | **u32** | `10` | Maximum number of *frames* without receiving data from other player before resetting the connection.
|
||||
`interval` | **u16** | `50` | Number of *1024-cycle ticks* (61.04μs) between transfers *(50 = 3.052ms)*. It's the interval of Timer #`sendTimerId`. Lower values will transfer faster but also consume more CPU. You can use `Link::perFrame(...)` to convert from *packets per frame* to *interval values*.
|
||||
`sendTimerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending.
|
||||
`asyncACKTimerId` | **s8** *(0~3 or -1)* | `-1` | GBA Timer to use for ACKs. If you have free timers, use one here to reduce CPU usage.
|
||||
|
||||
You can update these values at any time without creating a new instance:
|
||||
- Call `deactivate()`.
|
||||
|
|
@ -163,35 +207,42 @@ You can update these values at any time without creating a new instance:
|
|||
|
||||
You can also change these compile-time constants:
|
||||
- `LINK_WIRELESS_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing messages the queues can store at max). The default value is `30`, which seems fine for most games.
|
||||
- This affects how much memory is allocated. With the default value, it's around `960` bytes. There's a double-buffered incoming queue and a double-buffered outgoing queue (to avoid data races).
|
||||
- You can approximate the memory usage with:
|
||||
- `LINK_WIRELESS_QUEUE_SIZE * sizeof(Message) * 4` <=> `LINK_WIRELESS_QUEUE_SIZE * 32`
|
||||
- `LINK_WIRELESS_MAX_SERVER_TRANSFER_LENGTH` and `LINK_WIRELESS_MAX_CLIENT_TRANSFER_LENGTH`: to set the biggest allowed transfer per timer tick. Transfers contain retransmission headers and multiple user messages. These values must be in the range `[6;20]` for servers and `[2;4]` for clients. The default values are `20` and `4`, but you might want to set them a bit lower to reduce CPU usage.
|
||||
- `LINK_WIRELESS_PUT_ISR_IN_IWRAM`: to put critical functions (~3.5KB) in IWRAM, which can significantly improve performance due to its faster access. This is disabled by default to conserve IWRAM space, which is limited, but it's enabled in demos to showcase its performance benefits.
|
||||
- `LINK_WIRELESS_USE_SEND_RECEIVE_LATCH`: to alternate between sends and receives on each timer tick (instead of doing both things). This is disabled by default. Enabling it will introduce some latency but reduce overall CPU usage.
|
||||
- `LINK_WIRELESS_PUT_ISR_IN_IWRAM`: to put critical functions in IWRAM, which can significantly improve performance due to its faster access. This is disabled by default to conserve IWRAM space, which is limited, but it's enabled in demos to showcase its performance benefits.
|
||||
- If you enable this, make sure that `LinkWireless.cpp` gets compiled! For example, in a Makefile-based project, verify that the file is in your `SRCDIRS` list.
|
||||
- `LINK_WIRELESS_ENABLE_NESTED_IRQ`: to allow `LINK_WIRELESS_ISR_*` functions to be interrupted. This can be useful, for example, if your audio engine requires calling a VBlank handler with precise timing.
|
||||
- This won't produce any effect if `LINK_WIRELESS_PUT_ISR_IN_IWRAM` is disabled.
|
||||
- `LINK_WIRELESS_USE_SEND_RECEIVE_LATCH`: to alternate between sends and receives on each timer tick (instead of doing both things). This is disabled by default. Enabling it will introduce a bit of latency but also reduce _a lot_ the overall CPU usage. It's enabled in the `LinkWireless_demo` example, but disabled in the `LinkUniversal_*` examples.
|
||||
- `LINK_WIRELESS_TWO_PLAYERS_ONLY`: to optimize the library for two players. This will make the code smaller and use less CPU. It will also let you "misuse" `5` bits from the packet header to send small packets really fast (e.g. pressed keys) without confirmation, using the `QUICK_SEND` and `QUICK_RECEIVE` properties. When this option is enabled, the `maxPlayers` constructor parameter will be ignored.
|
||||
|
||||
## Methods
|
||||
|
||||
- Most of these methods return a boolean, indicating if the action was successful. If not, you can call `getLastError()` to know the reason. Usually, unless it's a trivial error (like buffers being full), the connection with the adapter is reset and the game needs to start again.
|
||||
- You can check the connection state at any time with `getState()`.
|
||||
- Until a session starts, all actions are synchronic.
|
||||
- During sessions (when the state is `SERVING` or `CONNECTED`), the message transfers are IRQ-driven, so `send(...)` and `receive(...)` won't waste extra cycles.
|
||||
- Until a session starts, all actions are synchronous.
|
||||
- During sessions (when the state is `SERVING` or `CONNECTED`), the message transfers are IRQ-driven, so `send(...)` and `receive(...)` won't waste extra cycles. The only synchronous method that can be called during a session is `serve(...)`, which can be used to update the broadcast data.
|
||||
|
||||
Name | Return type | Description
|
||||
--- | --- | ---
|
||||
`isActive()` | **bool** | Returns whether the library is active or not.
|
||||
`activate()` | **bool** | Activates the library. When an adapter is connected, it changes the state to `AUTHENTICATED`. It can also be used to disconnect or reset the adapter.
|
||||
`deactivate()` | **bool** | Puts the adapter into a low consumption mode and then deactivates the library. It returns a boolean indicating whether the transition to low consumption mode was successful.
|
||||
`serve([gameName], [userName], [gameId])` | **bool** | Starts broadcasting a server and changes the state to `SERVING`. You can, optionally, provide a `gameName` (max `14` characters), a `userName` (max `8` characters), and a `gameId` *(0 ~ 0x7FFF)* that games will be able to read. The strings must be null-terminated character arrays. If the adapter is already serving, this method only updates the broadcast data.
|
||||
`deactivate()` | **bool** | Puts the adapter into a low consumption mode and then deactivates the library. It returns a boolean indicating whether the transition to low consumption mode was successful. You can disable the transition and deactivate directly by setting `turnOff` to `true`.
|
||||
`serve([gameName], [userName], [gameId])` | **bool** | Starts broadcasting a server and changes the state to `SERVING`. You can, optionally, provide a `gameName` (max `14` characters), a `userName` (max `8` characters), and a `gameId` *(0 ~ 0x7FFF)* that games will be able to read. The strings must be null-terminated character arrays. If the adapter is already serving, this method only updates the broadcast data. Updating broadcast data while serving can fail if the adapter is busy. In that case, this will return `false` and `getLastError()` will be `BUSY_TRY_AGAIN`.
|
||||
`getServers(servers, [onWait])` | **bool** | Fills the `servers` array with all the currently broadcasting servers. This action takes 1 second to complete, but you can optionally provide an `onWait()` function which will be invoked each time VBlank starts.
|
||||
`getServersAsyncStart()` | **bool** | Starts looking for broadcasting servers and changes the state to `SEARCHING`. After this, call `getServersAsyncEnd(...)` 1 second later.
|
||||
`getServersAsyncEnd(servers)` | **bool** | Fills the `servers` array with all the currently broadcasting servers. Changes the state to `AUTHENTICATED` again.
|
||||
`connect(serverId)` | **bool** | Starts a connection with `serverId` and changes the state to `CONNECTING`.
|
||||
`keepConnecting()` | **bool** | When connecting, this needs to be called until the state is `CONNECTED`. It assigns a player id. Keep in mind that `isConnected()` and `playerCount()` won't be updated until the first message from server arrives.
|
||||
`keepConnecting()` | **bool** | When connecting, this needs to be called until the state is `CONNECTED`. It assigns a player ID. Keep in mind that `isConnected()` and `playerCount()` won't be updated until the first message from the server arrives.
|
||||
`send(data)` | **bool** | Enqueues `data` to be sent to other nodes.
|
||||
`receive(messages)` | **bool** | Fills the `messages` array with incoming messages, forwarding if needed.
|
||||
`getState()` | **LinkWireless::State** | Returns the current state (one of `LinkWireless::State::NEEDS_RESET`, `LinkWireless::State::AUTHENTICATED`, `LinkWireless::State::SEARCHING`, `LinkWireless::State::SERVING`, `LinkWireless::State::CONNECTING`, or `LinkWireless::State::CONNECTED`).
|
||||
`isConnected()` | **bool** | Returns true if the player count is higher than 1.
|
||||
`isSessionActive()` | **bool** | Returns true if the state is `SERVING` or `CONNECTED`.
|
||||
`isConnected()` | **bool** | Returns `true` if the player count is higher than `1`.
|
||||
`isSessionActive()` | **bool** | Returns `true` if the state is `SERVING` or `CONNECTED`.
|
||||
`playerCount()` | **u8** *(1~5)* | Returns the number of connected players.
|
||||
`currentPlayerId()` | **u8** *(0~4)* | Returns the current player id.
|
||||
`currentPlayerId()` | **u8** *(0~4)* | Returns the current player ID.
|
||||
`getLastError([clear])` | **LinkWireless::Error** | If one of the other methods returns `false`, you can inspect this to know the cause. After this call, the last error is cleared if `clear` is `true` (default behavior).
|
||||
|
||||
⚠️ `0xFFFF` is a reserved value, so don't send it!
|
||||
|
|
@ -202,6 +253,8 @@ Name | Return type | Description
|
|||
|
||||
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.
|
||||
|
||||
Its demo (`LinkWirelessMultiboot_demo`) has all the other gba-link-connection ROMs bundled with it, so it can be used to quickly test the library.
|
||||
|
||||
https://github.com/afska/gba-link-connection/assets/1631752/9a648bff-b14f-4a85-92d4-ccf366adce2d
|
||||
|
||||
## Methods
|
||||
|
|
@ -220,7 +273,7 @@ Name | Return type | Description
|
|||
|
||||
## Methods
|
||||
|
||||
- There's one method for every supported wireless adapter command.
|
||||
- There's one method for every supported Wireless Adapter command.
|
||||
- Use `sendCommand(...)` to send arbitrary commands.
|
||||
|
||||
# 🔧🏛 LinkWirelessOpenSDK
|
||||
|
|
@ -254,10 +307,11 @@ Name | Type | Default | Description
|
|||
`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](#-LinkCable) constructor parameters in one *struct*.
|
||||
`wirelessOptions` | **LinkUniversal::WirelessOptions** | *same as LinkWireless* | All the [📻 LinkWireless](#-LinkWireless) constructor parameters in one *struct*.
|
||||
`randomSeed` | **int** | `123` | Random seed used for waits to prevent livelocks. If you use _libtonc_, pass `__qran_seed`.
|
||||
|
||||
You can also change these compile-time constants:
|
||||
- `LINK_UNIVERSAL_MAX_PLAYERS`: to set a maximum number of players. The default value is `5`, but since LinkCable's limit is `4`, you might want to decrease it.
|
||||
- `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.
|
||||
- `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
|
||||
|
||||
|
|
@ -268,8 +322,8 @@ Name | Return type | Description
|
|||
`getState()` | **LinkUniversal::State** | Returns the current state (one of `LinkUniversal::State::INITIALIZING`, `LinkUniversal::State::WAITING`, or `LinkUniversal::State::CONNECTED`).
|
||||
`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](#-LinkWireless)'s `getState()`).
|
||||
`setProtocol(protocol)` | - | Sets the active `protocol`.
|
||||
|
||||
# 🔌 LinkGPIO
|
||||
|
||||
|
|
@ -298,7 +352,7 @@ Name | Return type | Description
|
|||
|
||||
*(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`.
|
||||
This is the GBA's implementation of SPI. You can use this to interact with other GBAs or computers that know SPI.
|
||||
|
||||

|
||||
|
||||
|
|
@ -307,26 +361,29 @@ This is the GBA's implementation of SPI. You can use this to interact with other
|
|||
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(mode, [dataSize])` | - | Activates the library in a specific `mode` (one of `LinkSPI::Mode::SLAVE`, `LinkSPI::Mode::MASTER_256KBPS`, or `LinkSPI::Mode::MASTER_2MBPS`). By default, the `dataSize` is 32-bit, but can be changed to `LinkSPI::DataSize::SIZE_8BIT`.
|
||||
`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`.
|
||||
`getAsyncData()` | **u32** | If the async state is `READY`, returns the remote data and switches the state back to `IDLE`. If not, returns an empty response.
|
||||
`getMode()` | **LinkSPI::Mode** | Returns the current `mode`.
|
||||
`getDataSize()` | **LinkSPI::DataSize** | Returns the current `dataSize`.
|
||||
`setWaitModeActive(isActive)` | - | Enables or disables `waitMode` (*).
|
||||
`isWaitModeActive()` | **bool** | Returns whether `waitMode` (*) is active or not.
|
||||
|
||||
> (*) `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`: The GBA adds an extra feature over SPI. When working as master, it can check whether the other terminal is ready to receive (ready: `MISO=LOW`), and wait if it's not (not ready: `MISO=HIGH`). 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.
|
||||
>
|
||||
> `MISO` means `SO` on the slave side and `SI` on the master side.
|
||||
|
||||
⚠️ 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!
|
||||
⚠️ returns `0xFFFFFFFF` (or `0xFF`) on misuse or cancelled transfers!
|
||||
|
||||
## SPI Configuration
|
||||
|
||||
|
|
@ -377,17 +434,110 @@ Name | Return type | Description
|
|||
|
||||
## UART Configuration
|
||||
|
||||
The GBA operates using 1 stop bit, but everything else can be configured. By default, the library uses `8N1`, which means 8-bit data and no parity bit. RTS/CTS is disabled by default.
|
||||
The GBA operates using `1` stop bit, but everything else can be configured. By default, the library uses `8N1`, which means 8-bit data and no parity bit. RTS/CTS is disabled by default.
|
||||
|
||||

|
||||
|
||||
- Black wire (GND) -> GBA GND.
|
||||
- Green wire (TX) -> GBA SI.
|
||||
- White wire (RX) -> GBA SO.
|
||||
- Black wire (`GND`) -> GBA `GND`.
|
||||
- Green wire (`TX`) -> GBA `SI`.
|
||||
- White wire (`RX`) -> GBA `SO`.
|
||||
|
||||
# 🟪 LinkCube
|
||||
|
||||
*(aka JOYBUS Mode)*
|
||||
|
||||
This is the GBA's implementation of JOYBUS, in which users connect the console to a *GameCube* (or *Wii* with GC ports) using an official adapter. The library can be tested using *Dolphin/mGBA* and [gba-joybus-tester](https://github.com/afska/gba-joybus-tester).
|
||||
|
||||

|
||||
|
||||
You can change these compile-time constants:
|
||||
- `LINK_CUBE_QUEUE_SIZE`: to set a custom buffer size (how many incoming and outgoing values the queues can store at max). The default value is `10`, which seems fine for most games.
|
||||
- This affects how much memory is allocated. With the default value, it's around `120` bytes. There's a double-buffered pending queue (to avoid data races), and 1 outgoing queue.
|
||||
- You can approximate the memory usage with:
|
||||
- `LINK_CUBE_QUEUE_SIZE * sizeof(u32) * 3` <=> `LINK_CUBE_QUEUE_SIZE * 12`
|
||||
|
||||
## Methods
|
||||
|
||||
Name | Return type | Description
|
||||
--- | --- | ---
|
||||
`isActive()` | **bool** | Returns whether the library is active or not.
|
||||
`activate()` | - | Activates the library.
|
||||
`deactivate()` | - | Deactivates the library.
|
||||
`wait()` | **bool** | Waits for data. Returns `true` on success, or `false` on JOYBUS reset.
|
||||
`wait(cancel)` | **bool** | Like `wait()` but accepts a `cancel()` function. The library will invoke it after every SERIAL interrupt, and abort the wait if it returns `true`.
|
||||
`canRead()` | **bool** | Returns `true` if there are pending received values to read.
|
||||
`read()` | **u32** | Dequeues and returns the next received value. If there's no received data, a `0` will be returned.
|
||||
`peek()` | **u32** | Returns the next received value without dequeuing it. If there's no received data, a `0` will be returned.
|
||||
`send(data)` | - | Sends 32-bit `data`. If the other end asks for data at the same time you call this method, a `0x00000000` will be sent.
|
||||
`pendingCount()` | **u32** | Returns the number of pending outgoing transfers.
|
||||
`didReset([clear])` | **bool** | Returns whether a JOYBUS reset was requested or not. After this call, the reset flag is cleared if `clear` is `true` (default behavior).
|
||||
|
||||
# 📱 LinkMobile
|
||||
|
||||
*(aka Mobile Adapter GB)*
|
||||
|
||||
This is a driver for an accessory that enables online conectivity on the GB and GBA. The protocol was reverse-engineered by the *REON Team*.
|
||||
|
||||
The original accessory was sold in Japan only and using it nowadays is hard since it relies on old tech, but REON has created an open-source implementation called [libmobile](https://github.com/REONTeam/libmobile), as well as support for emulators and microcontrollers.
|
||||
|
||||
It has two modes of operation:
|
||||
- Direct call (P2P): Calling someone directly for a 2-player session, using the other person's IP address or the phone number provided by the [relay server](https://github.com/REONTeam/mobile-relay).
|
||||
- ISP call (PPP): Calling an ISP number for internet access. In this mode, the adapter can open up to 2 TCP/UDP sockets and transfer arbitrary data.
|
||||
|
||||

|
||||
|
||||
## Constructor
|
||||
|
||||
`new LinkMobile(...)` accepts these **optional** parameters:
|
||||
|
||||
Name | Type | Default | Description
|
||||
--- | --- | --- | ---
|
||||
`timeout` | **u32** | `600` | Number of *frames* without completing a request to reset a connection.
|
||||
`timerId` | **u8** *(0~3)* | `3` | GBA Timer to use for sending.
|
||||
|
||||
You can update these values at any time without creating a new instance:
|
||||
- Call `deactivate()`.
|
||||
- Mutate the `config` property.
|
||||
- Call `activate()`.
|
||||
|
||||
You can also change these compile-time constants:
|
||||
- `LINK_MOBILE_QUEUE_SIZE`: to set a custom request queue size (how many commands can be queued at the same time). The default value is `10`, which seems fine for most games.
|
||||
- This affects how much memory is allocated. With the default value, it's around `3` KB.
|
||||
|
||||
## Methods
|
||||
|
||||
- All actions are asynchronous/nonblocking. That means, they will return `true` if nothing is awfully wrong, but the actual consequence will occur some frames later. You can call `getState()` at any time to know what it's doing.
|
||||
- On fatal errors, the library will transition to a `NEEDS_RESET` state. In that case, you can call `getError()` to know more details on what happened, and then `activate()` to restart.
|
||||
- When calling `deactivate()`, the adapter automatically turns itself off after `3` seconds of inactivity. However, to gracefully turn it off, it's recommended to call `shutdown()` first, wait until the state is `SHUTDOWN`, and then `deactivate()`.
|
||||
|
||||
Name | Return type | Description
|
||||
--- | --- | ---
|
||||
`isActive()` | **bool** | Returns whether the library is active or not.
|
||||
`activate()` | - | Activates the library. After some time, if an adapter is connected, the state will be changed to `SESSION_ACTIVE`. If not, the state will be `NEEDS_RESET`, and you can retrieve the error with `getError()`.
|
||||
`deactivate()` | - | Deactivates the library, resetting the serial mode to GPIO. Calling `shutdown()` first is recommended, but the adapter will put itself in sleep mode after 3 seconds anyway.
|
||||
`shutdown()` | **bool** | Gracefully shuts down the adapter, closing all connections. After some time, the state will be changed to `SHUTDOWN`, and only then it's safe to call `deactivate()`.
|
||||
`call(phoneNumber)` | **bool** | Initiates a P2P connection with a `phoneNumber`. After some time, the state will be `CALL_ESTABLISHED` (or `ACTIVE_SESSION` if the connection fails or ends). In REON/libmobile the phone number can be a number assigned by the relay server, or a 12-digit IPv4 address (for example, `"127000000001"` would be `127.0.0.1`).
|
||||
`callISP(password, loginId)` | **bool** | Calls the ISP number registered in the adapter configuration, or a default number if the adapter hasn't been configured. Then, performs a login operation using the provided `password` and `loginId`. After some time, the state will be `PPP_ACTIVE`. If `loginId` is empty and the adapter has been configured, it will use the one stored in the configuration. Both parameters are null-terminated strings (max `32` characters).
|
||||
`dnsQuery(domainName, result)` | **bool** | Looks up the IPv4 address for a `domainName` (a null-terminated string, max `253` characters). It also accepts an ASCII IPv4 address, converting it into a 4-byte address instead of querying the DNS server. The `result` is a pointer to a `LinkMobile::DNSQuery` struct that will be filled with the result. When the request is completed, the `completed` field will be `true`. If an IP address was found, the `success` field will be `true` and the `ipv4` field can be read as a 4-byte address.
|
||||
`openConnection(ip, port, type, result)` | **bool** | Opens a TCP/UDP (`type`) connection at the given `ip` (4-byte address) on the given `port`. The `result` is a pointer to a `LinkMobile::OpenConn` struct that will be filled with the result. When the request is completed, the `completed` field will be `true`. If the connection was successful, the `success` field will be `true` and the `connectionId` field can be used when calling the `transfer(...)` method. Only `2` connections can be opened at the same time.
|
||||
`closeConnection(connectionId, type, result)` | **bool** | Closes an active TCP/UDP (`type`) connection. The `result` is a pointer to a `LinkMobile::CloseConn` struct that will be filled with the result. When the request is completed, the `completed` field will be `true`. If the connection was closed correctly, the `success` field will be `true`.
|
||||
`transfer(dataToSend, result, [connectionId])` | **bool** | Requests a data transfer (up to `254` bytes) and responds the received data. The transfer can be done with the other node in a P2P connection, or with any open TCP/UDP connection if a PPP session is active. In the case of a TCP/UDP connection, the `connectionId` must be provided. The `result` is a pointer to a `LinkMobile::DataTransfer` struct that will be filled with the received data. It can also point to `dataToSend` to reuse the struct. When the request is completed, the `completed` field will be `true`. If the transfer was successful, the `success` field will be `true`. If not, you can assume that the connection was closed.
|
||||
`waitFor(asyncRequest)` | **bool** | Waits for `asyncRequest` to be completed. Returns `true` if the request was completed && successful, and the adapter session is still alive. Otherwise, it returns `false`. The `asyncRequest` is a pointer to a `LinkMobile::DNSQuery`, `LinkMobile::OpenConn`, `LinkMobile::CloseConn`, or `LinkMobile::DataTransfer`.
|
||||
`hangUp()` | **bool** | Hangs up the current P2P or PPP call. Closes all connections.
|
||||
`readConfiguration(configurationData)` | **bool** | Retrieves the adapter configuration, and puts it in the `configurationData` struct. If the adapter has an active session, the data is already loaded, so it's instantaneous.
|
||||
`getState()` | **LinkMobile::State** | Returns the current state (one of `LinkMobile::State::NEEDS_RESET`, `LinkMobile::State::PINGING`, `LinkMobile::State::WAITING_TO_START`, `LinkMobile::State::STARTING_SESSION`, `LinkMobile::State::ACTIVATING_SIO32`, `LinkMobile::State::WAITING_32BIT_SWITCH`, `LinkMobile::State::READING_CONFIGURATION`, `LinkMobile::State::SESSION_ACTIVE`, `LinkMobile::State::CALL_REQUESTED`, `LinkMobile::State::CALLING`, `LinkMobile::State::CALL_ESTABLISHED`, `LinkMobile::State::ISP_CALL_REQUESTED`, `LinkMobile::State::ISP_CALLING`, `LinkMobile::State::PPP_LOGIN`, `LinkMobile::State::PPP_ACTIVE`, `LinkMobile::State::SHUTDOWN_REQUESTED`, `LinkMobile::State::ENDING_SESSION`, `LinkMobile::State::WAITING_8BIT_SWITCH`, or `LinkMobile::State::SHUTDOWN`).
|
||||
`getRole()` | **LinkMobile::Role** | Returns the current role in the P2P connection (one of `LinkMobile::Role::NO_P2P_CONNECTION`, `LinkMobile::Role::CALLER`, or `LinkMobile::Role::RECEIVER`).
|
||||
`isConfigurationValid()` | **int** | Returns whether the adapter has been configured or not. Returns `1` = yes, `0` = no, `-1` = unknown (no session active).
|
||||
`isConnectedP2P()` | **bool** | Returns `true` if a P2P call is established (the state is `CALL_ESTABLISHED`).
|
||||
`isConnectedPPP()` | **bool** | Returns `true` if a PPP session is active (the state is `PPP_ACTIVE`).
|
||||
`isSessionActive()` | **bool** | Returns `true` if the session is active.
|
||||
`canShutdown()` | **bool** | Returns `true` if there's an active session and there's no previous shutdown requests.
|
||||
`getDataSize()` | **LinkSPI::DataSize** | Returns the current operation mode (`LinkSPI::DataSize`).
|
||||
`getError()` | **LinkMobile::Error** | Returns details about the last error that caused the connection to be aborted.
|
||||
|
||||
# 🖱️ LinkPS2Mouse
|
||||
|
||||
A PS/2 mouse driver for the GBA. Use it to add mouse support to your homebrew games.
|
||||
A PS/2 mouse driver for the GBA. Use it to add mouse support to your homebrew games. It's a straight port from [this library](https://github.com/kristopher/PS2-Mouse-Arduino).
|
||||
|
||||

|
||||
|
||||
|
|
@ -402,7 +552,21 @@ Name | Return type | Description
|
|||
`isActive()` | **bool** | Returns whether the library is active or not.
|
||||
`activate()` | - | Activates the library.
|
||||
`deactivate()` | - | Deactivates the library.
|
||||
`report(data[3])` | - | Fills the `data` int array with a report. The first int contains _clicks_ that you can check against the bitmasks `LINK_PS2_MOUSE_LEFT_CLICK`, `LINK_PS2_MOUSE_MIDDLE_CLICK`, and `LINK_PS2_MOUSE_RIGHT_CLICK`. The second int is the _X movement_, and the third int is the _Y movement_.
|
||||
`report(data[3])` | - | Fills the `data` int array with a report. The first int contains *clicks* that you can check against the bitmasks `LINK_PS2_MOUSE_LEFT_CLICK`, `LINK_PS2_MOUSE_MIDDLE_CLICK`, and `LINK_PS2_MOUSE_RIGHT_CLICK`. The second int is the *X movement*, and the third int is the *Y movement*.
|
||||
|
||||
⚠️ calling `activate()` or `report(...)` could freeze the system if nothing is connected: detecting timeouts using interrupts is the user's responsibility.
|
||||
|
||||
## Pinout
|
||||
|
||||
```
|
||||
____________
|
||||
|PS/2 --- GBA|
|
||||
|------------|
|
||||
|CLOCK -> SI |
|
||||
|DATA --> SO |
|
||||
|VCC ---> VCC|
|
||||
|GND ---> GND|
|
||||
```
|
||||
|
||||
# ⌨️ LinkPS2Keyboard
|
||||
|
||||
|
|
@ -412,7 +576,7 @@ A PS/2 keyboard driver for the GBA. Use it to add keyboard support to your homeb
|
|||
|
||||
## Constructor
|
||||
|
||||
`new LinkPS2Keyboard(onEvent)`, where `onEvent` is a function pointer that will receive the scan codes (`u8`). You should check a PS/2 scan code list online, but there are some examples included like `LINK_PS2_KEYBOARD_KEY_ENTER` and `LINK_PS2_KEYBOARD_KEY_RELEASE`.
|
||||
`new LinkPS2Keyboard(onEvent)`, where `onEvent` is a function pointer that will receive the scan codes (`u8`). You should check a PS/2 scan code list online, but most common keys/events are included in enums like `LINK_PS2_KEYBOARD_KEY::ENTER` and `LINK_PS2_KEYBOARD_EVENT::RELEASE`.
|
||||
|
||||
## Methods
|
||||
|
||||
|
|
@ -420,4 +584,16 @@ Name | Return type | Description
|
|||
--- | --- | ---
|
||||
`isActive()` | **bool** | Returns whether the library is active or not.
|
||||
`activate()` | - | Activates the library.
|
||||
`deactivate()` | - | Deactivates the library.
|
||||
`deactivate()` | - | Deactivates the library.
|
||||
|
||||
## Pinout
|
||||
|
||||
```
|
||||
____________
|
||||
|PS/2 --- GBA|
|
||||
|------------|
|
||||
|CLOCK -> SI |
|
||||
|DATA --> SO |
|
||||
|VCC ---> VCC|
|
||||
|GND ---> GND|
|
||||
```
|
||||
|
|
|
|||
213
docs/gbatek.md
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
The GBAs Serial Port may be used in various different communication modes. Normal mode may exchange data between two GBAs (or to transfer data from master GBA to several slave GBAs in one-way direction).
|
||||
Multi-player mode may exchange data between up to four GBAs. UART mode works much like a RS232 interface. JOY Bus mode uses a standardized Nintendo protocol. And General Purpose mode allows to mis-use the 'serial' port as bi-directional 4bit parallel port.
|
||||
Note: The Nintendo DS does not include a Serial Port.
|
||||
|
||||
Note: The Nintendo DS does not include a Serial Port.
|
||||
|
||||
- SIO Normal Mode
|
||||
- SIO Multi-Player Mode
|
||||
- SIO General-Purpose Mode
|
||||
|
|
@ -13,12 +13,12 @@ Note: The Nintendo DS does not include a Serial Port.
|
|||
- GBA Wireless Adapter
|
||||
|
||||
# SIO Normal Mode
|
||||
|
||||
|
||||
This mode is used to communicate between two units.
|
||||
Transfer rates of 256Kbit/s or 2Mbit/s can be selected, however, the fast 2Mbit/s is intended ONLY for special hardware expansions that are DIRECTLY connected to the GBA link port (ie. without a cable being located between the GBA and expansion hardware). In normal cases, always use 256Kbit/s transfer rate which provides stable results.
|
||||
Transfer lengths of 8bit or 32bit may be used, the 8bit mode is the same as for older DMG/CGB gameboys, however, the voltages for "GBA cartridges in GBAs" are different as for "DMG/CGB cartridges in DMG/CGB/GBAs", ie. it is not possible to communicate between DMG/CGB games and GBA games.
|
||||
Transfer lengths of 8bit or 32bit may be used, the 8bit mode is the same as for older DMG/CGB gameboys, however, the voltages for "GBA cartridges in GBAs" are different as for "DMG/CGB cartridges in DMG/CGB/GBAs", ie. it is not possible to communicate between DMG/CGB games and GBA games.
|
||||
|
||||
**4000134h - RCNT (R) - Mode Selection, in Normal/Multiplayer/UART modes (R/W)**
|
||||
**4000134h - RCNT (R) - Mode Selection, in Normal/Multiplayer/UART modes (R/W)**
|
||||
|
||||
```
|
||||
Bit Expl.
|
||||
|
|
@ -29,7 +29,7 @@ Transfer lengths of 8bit or 32bit may be used, the 8bit mode is the same as for
|
|||
15 Must be zero (0) for Normal/Multiplayer/UART modes
|
||||
```
|
||||
|
||||
**4000128h - SIOCNT - SIO Control, usage in NORMAL Mode (R/W)**
|
||||
**4000128h - SIOCNT - SIO Control, usage in NORMAL Mode (R/W)**
|
||||
|
||||
```
|
||||
Bit Expl.
|
||||
|
|
@ -46,23 +46,23 @@ Transfer lengths of 8bit or 32bit may be used, the 8bit mode is the same as for
|
|||
15 Not used (Read only, always 0)
|
||||
```
|
||||
|
||||
The Start bit is automatically reset when the transfer completes, ie. when all 8 or 32 bits are transferred, at that time an IRQ may be generated.
|
||||
|
||||
**400012Ah - SIODATA8 - SIO Normal Communication 8bit Data (R/W)**
|
||||
The Start bit is automatically reset when the transfer completes, ie. when all 8 or 32 bits are transferred, at that time an IRQ may be generated.
|
||||
|
||||
For 8bit normal mode. Contains 8bit data (only lower 8bit are used). Outgoing data should be written to this register before starting the transfer. During transfer, transmitted bits are shifted-out (MSB first), and received bits are shifted-in simultaneously. Upon transfer completion, the register contains the received 8bit value.
|
||||
|
||||
**4000120h - SIODATA32\_L - SIO Normal Communication lower 16bit data (R/W)**
|
||||
**4000122h - SIODATA32\_H - SIO Normal Communication upper 16bit data (R/W)**
|
||||
**400012Ah - SIODATA8 - SIO Normal Communication 8bit Data (R/W)**
|
||||
|
||||
For 8bit normal mode. Contains 8bit data (only lower 8bit are used). Outgoing data should be written to this register before starting the transfer. During transfer, transmitted bits are shifted-out (MSB first), and received bits are shifted-in simultaneously. Upon transfer completion, the register contains the received 8bit value.
|
||||
|
||||
**4000120h - SIODATA32_L - SIO Normal Communication lower 16bit data (R/W)**
|
||||
**4000122h - SIODATA32_H - SIO Normal Communication upper 16bit data (R/W)**
|
||||
|
||||
Same as above SIODATA8, for 32bit normal transfer mode respectively.
|
||||
SIOCNT/RCNT must be set to 32bit normal mode <before> writing to SIODATA32.
|
||||
|
||||
SIOCNT/RCNT must be set to 32bit normal mode <before> writing to SIODATA32.
|
||||
|
||||
**Initialization**
|
||||
|
||||
First, initialize RCNT register. Second, set mode/clock bits in SIOCNT with startbit cleared. For master: select internal clock, and (in most cases) specify 256KHz as transfer rate. For slave: select external clock, the local transfer rate selection is then ignored, as the transfer rate is supplied by the remote GBA (or other computer, which might supply custom transfer rates).
|
||||
Third, set the startbit in SIOCNT with mode/clock bits unchanged.
|
||||
|
||||
Third, set the startbit in SIOCNT with mode/clock bits unchanged.
|
||||
|
||||
**Recommended Communication Procedure for SLAVE unit (external clock)**
|
||||
\- Initialize data which is to be sent to master.
|
||||
\- Set Start flag.
|
||||
|
|
@ -71,41 +71,41 @@ Third, set the startbit in SIOCNT with mode/clock bits unchanged.
|
|||
\- Set SO to HIGH to indicate that we are not ready.
|
||||
\- Process received data.
|
||||
\- Repeat procedure if more data is to be transferred.
|
||||
(or is so=high done automatically? would be fine - more stable - otherwise master may still need delay)
|
||||
|
||||
(or is so=high done automatically? would be fine - more stable - otherwise master may still need delay)
|
||||
|
||||
**Recommended Communication Procedure for SLAVE unit (external clock)**
|
||||
\- Initialize data which is to be sent to master.
|
||||
\- Set Start=0 and SO=0 (SO=LOW indicates that slave is (almost) ready).
|
||||
\- Set Start=1 and SO=1 (SO=HIGH indicates not ready, applied after transfer).
|
||||
\- Set Start=1 and SO=1 (SO=HIGH indicates not ready, applied after transfer).
|
||||
|
||||
> (Expl. Old SO=LOW kept output until 1st clock bit received).
|
||||
> (Expl. Old SO=LOW kept output until 1st clock bit received).
|
||||
|
||||
> (Expl. New SO=HIGH is automatically output at transfer completion).
|
||||
> (Expl. New SO=HIGH is automatically output at transfer completion).
|
||||
|
||||
\- Set SO to LOW to indicate that master may start now.
|
||||
\- Wait for IRQ (or for Start bit to become zero). (Check timeout here!)
|
||||
\- Process received data.
|
||||
\- Repeat procedure if more data is to be transferred.
|
||||
|
||||
\- Repeat procedure if more data is to be transferred.
|
||||
|
||||
**Recommended Communication Procedure for MASTER unit (internal clock)**
|
||||
\- Initialize data which is to be sent to slave.
|
||||
\- Wait for SI to become LOW (slave ready). (Check timeout here!)
|
||||
\- Set Start flag.
|
||||
\- Wait for IRQ (or for Start bit to become zero).
|
||||
\- Process received data.
|
||||
\- Repeat procedure if more data is to be transferred.
|
||||
|
||||
\- Repeat procedure if more data is to be transferred.
|
||||
|
||||
**Cable Protocol**
|
||||
During inactive transfer, the shift clock (SC) is high. The transmit (SO) and receive (SI) data lines may be manually controlled as described above.
|
||||
When master sends SC=LOW, each master and slave must output the next outgoing data bit to SO. When master sends SC=HIGH, each master and slave must read out the opponents data bit from SI. This is repeated for each of the 8 or 32 bits, and when completed SC will be kept high again.
|
||||
|
||||
When master sends SC=LOW, each master and slave must output the next outgoing data bit to SO. When master sends SC=HIGH, each master and slave must read out the opponents data bit from SI. This is repeated for each of the 8 or 32 bits, and when completed SC will be kept high again.
|
||||
|
||||
**Transfer Rates**
|
||||
Either 256KHz or 2MHz rates can be selected for SC, so max 32KBytes (256Kbit) or 128KBytes (2Mbit) can be transferred per second. However, the software must process each 8bit or 32bit of transmitted data separately, so the actual transfer rate will be reduced by the time spent on handling each data unit.
|
||||
Only 256KHz provides stable results in most cases (such like when linking between two GBAs). The 2MHz rate is intended for special expansion hardware (with very short wires) only.
|
||||
|
||||
Only 256KHz provides stable results in most cases (such like when linking between two GBAs). The 2MHz rate is intended for special expansion hardware (with very short wires) only.
|
||||
|
||||
**Using Normal mode for One-Way Multiplayer communication**
|
||||
When using normal mode with multiplay-cables, data isn't exchanged between first and second GBA as usually. Instead, data is shifted from first to last GBA (the first GBA receives zero, because master SI is shortcut to GND).
|
||||
This behaviour may be used for fast ONE-WAY data transfer from master to all other GBAs. For example (3 GBAs linked):
|
||||
This behaviour may be used for fast ONE-WAY data transfer from master to all other GBAs. For example (3 GBAs linked):
|
||||
|
||||
```
|
||||
Step Sender 1st Recipient 2nd Recipient
|
||||
|
|
@ -116,13 +116,13 @@ This behaviour may be used for fast ONE-WAY data transfer from master to all oth
|
|||
```
|
||||
|
||||
The recipients should not output any own data, instead they should forward the previously received data to the next recipient during next transfer (just keep the incoming data unmodified in the data register).
|
||||
Due to the delayed forwarding, 2nd recipient should ignore the first incoming data. After the last transfer, the sender must send one (or more) dummy data unit(s), so that the last data is forwarded to the 2nd (or further) recipient(s).
|
||||
Due to the delayed forwarding, 2nd recipient should ignore the first incoming data. After the last transfer, the sender must send one (or more) dummy data unit(s), so that the last data is forwarded to the 2nd (or further) recipient(s).
|
||||
|
||||
# SIO Multi-Player Mode
|
||||
|
||||
Multi-Player mode can be used to communicate between up to 4 units.
|
||||
|
||||
**4000134h - RCNT (R) - Mode Selection, in Normal/Multiplayer/UART modes (R/W)**
|
||||
|
||||
Multi-Player mode can be used to communicate between up to 4 units.
|
||||
|
||||
**4000134h - RCNT (R) - Mode Selection, in Normal/Multiplayer/UART modes (R/W)**
|
||||
|
||||
```
|
||||
Bit Expl.
|
||||
|
|
@ -133,9 +133,9 @@ Multi-Player mode can be used to communicate between up to 4 units.
|
|||
15 Must be zero (0) for Normal/Multiplayer/UART modes
|
||||
```
|
||||
|
||||
Note: Even though undocumented, many Nintendo games are using Bit 0 to test current SC state in multiplay mode.
|
||||
|
||||
**4000128h - SIOCNT - SIO Control, usage in MULTI-PLAYER Mode (R/W)**
|
||||
Note: Even though undocumented, many Nintendo games are using Bit 0 to test current SC state in multiplay mode.
|
||||
|
||||
**4000128h - SIOCNT - SIO Control, usage in MULTI-PLAYER Mode (R/W)**
|
||||
|
||||
```
|
||||
Bit Expl.
|
||||
|
|
@ -152,34 +152,34 @@ Note: Even though undocumented, many Nintendo games are using Bit 0 to test curr
|
|||
15 Not used (Read only, always 0)
|
||||
```
|
||||
|
||||
The ID Bits are undefined until the first transfer has completed.
|
||||
|
||||
**400012Ah - SIOMLT\_SEND - Data Send Register (R/W)**
|
||||
Outgoing data (16 bit) which is to be sent to the other GBAs.
|
||||
|
||||
The ID Bits are undefined until the first transfer has completed.
|
||||
|
||||
**400012Ah - SIOMLT_SEND - Data Send Register (R/W)**
|
||||
Outgoing data (16 bit) which is to be sent to the other GBAs.
|
||||
|
||||
**4000120h - SIOMULTI0 - SIO Multi-Player Data 0 (Parent) (R/W)**
|
||||
**4000122h - SIOMULTI1 - SIO Multi-Player Data 1 (1st child) (R/W)**
|
||||
**4000124h - SIOMULTI2 - SIO Multi-Player Data 2 (2nd child) (R/W)**
|
||||
**4000126h - SIOMULTI3 - SIO Multi-Player Data 3 (3rd child) (R/W)**
|
||||
These registers are automatically reset to FFFFh upon transfer start.
|
||||
After transfer, these registers contain incoming data (16bit each) from all remote GBAs (if any / otherwise still FFFFh), as well as the local outgoing SIOMLT\_SEND data.
|
||||
Ie. after the transfer, all connected GBAs will contain the same values in their SIOMULTI0-3 registers.
|
||||
|
||||
After transfer, these registers contain incoming data (16bit each) from all remote GBAs (if any / otherwise still FFFFh), as well as the local outgoing SIOMLT_SEND data.
|
||||
Ie. after the transfer, all connected GBAs will contain the same values in their SIOMULTI0-3 registers.
|
||||
|
||||
**Initialization**
|
||||
\- Initialize RCNT Bit 14-15 and SIOCNT Bit 12-13 to select Multi-Player mode.
|
||||
\- Read SIOCNT Bit 3 to verify that all GBAs are in Multi-Player mode.
|
||||
\- Read SIOCNT Bit 2 to detect whether this is the Parent/Master unit.
|
||||
|
||||
\- Read SIOCNT Bit 2 to detect whether this is the Parent/Master unit.
|
||||
|
||||
**Recommended Transmission Procedure**
|
||||
\- Write outgoing data to SIODATA\_SEND.
|
||||
\- Write outgoing data to SIODATA_SEND.
|
||||
\- Master must set Start bit.
|
||||
\- All units must process received data in SIOMULTI0-3 when transfer completed.
|
||||
\- After the first successful transfer, ID Bits in SIOCNT are valid.
|
||||
\- If more data is to be transferred, repeat procedure.
|
||||
|
||||
The parent unit blindly sends data regardless of whether childs have already processed old data/supplied new data. So, parent unit might be required to insert delays between each transfer, and/or perform error checking.
|
||||
Also, slave units may signalize that they are not ready by temporarily switching into another communication mode (which does not output SD High, as Multi-Player mode does during inactivity).
|
||||
|
||||
Also, slave units may signalize that they are not ready by temporarily switching into another communication mode (which does not output SD High, as Multi-Player mode does during inactivity).
|
||||
|
||||
**Transfer Protocol**
|
||||
Beginning
|
||||
\- The masters SI pin is always LOW.
|
||||
|
|
@ -211,15 +211,15 @@ Step D
|
|||
Transfer end
|
||||
\- Master sets SC=HIGH, all GBAs set SO=HIGH.
|
||||
\- The Start/Busy bits of all GBAs are automatically cleared.
|
||||
\- Interrupts are requested in all GBAs (as far as enabled).
|
||||
|
||||
\- Interrupts are requested in all GBAs (as far as enabled).
|
||||
|
||||
**Error Bit**
|
||||
This bit is set when a slave did not receive SI=LOW even though SC=LOW signalized a transfer (this might happen when connecting more than 4 GBAs, or when the previous child is not connected). Also, the bit is set when a Stopbit wasn't HIGH.
|
||||
The error bit may be undefined during active transfer - read only after transfer completion (the transfer continues and completes as normal even if errors have occurred for some or all GBAs).
|
||||
Don't know: The bit is automatically reset/initialized with each transfer, or must be manually reset?
|
||||
|
||||
Don't know: The bit is automatically reset/initialized with each transfer, or must be manually reset?
|
||||
|
||||
**Transmission Time**
|
||||
The transmission time depends on the selected Baud rate. And on the amount of Bits (16 data bits plus start/stop bits for each GBA), delays between data for each GBA, plus final timeout (if less than 4 GBAs). That is, depending on the number of connected GBAs:
|
||||
The transmission time depends on the selected Baud rate. And on the amount of Bits (16 data bits plus start/stop bits for each GBA), delays between data for each GBA, plus final timeout (if less than 4 GBAs). That is, depending on the number of connected GBAs:
|
||||
|
||||
```
|
||||
GBAs Bits Delays Timeout
|
||||
|
|
@ -230,17 +230,17 @@ The transmission time depends on the selected Baud rate. And on the amount of Bi
|
|||
```
|
||||
|
||||
(The average Delay and Timeout periods are unknown?)
|
||||
Above is not counting the additional CPU time that must be spent on initiating and processing each transfer.
|
||||
|
||||
Above is not counting the additional CPU time that must be spent on initiating and processing each transfer.
|
||||
|
||||
**Fast One-Way Transmission**
|
||||
Beside for the actual SIO Multiplayer mode, you can also use SIO Normal mode for fast one-way data transfer from Master unit to all Child unit(s). See chapter about SIO Normal mode for details.
|
||||
|
||||
Beside for the actual SIO Multiplayer mode, you can also use SIO Normal mode for fast one-way data transfer from Master unit to all Child unit(s). See chapter about SIO Normal mode for details.
|
||||
|
||||
# SIO General-Purpose Mode
|
||||
|
||||
In this mode, the SIO is 'misused' as a 4bit bi-directional parallel port, each of the SI,SO,SC,SD pins may be directly controlled, each can be separately declared as input (with internal pull-up) or as output signal.
|
||||
|
||||
In this mode, the SIO is 'misused' as a 4bit bi-directional parallel port, each of the SI,SO,SC,SD pins may be directly controlled, each can be separately declared as input (with internal pull-up) or as output signal.
|
||||
|
||||
**4000134h - RCNT (R) - SIO Mode, usage in GENERAL-PURPOSE Mode (R/W)**
|
||||
Interrupts can be requested when SI changes from HIGH to LOW, as General Purpose mode does not require a serial shift clock, this interrupt may be produced even when the GBA is in Stop (low power standby) state.
|
||||
Interrupts can be requested when SI changes from HIGH to LOW, as General Purpose mode does not require a serial shift clock, this interrupt may be produced even when the GBA is in Stop (low power standby) state.
|
||||
|
||||
```
|
||||
Bit Expl.
|
||||
|
|
@ -258,14 +258,14 @@ Interrupts can be requested when SI changes from HIGH to LOW, as General Purpose
|
|||
15 Must be "1" for General-Purpose or JOYBUS Mode
|
||||
```
|
||||
|
||||
SI should be always used as Input to avoid problems with other hardware which does not expect data to be output there.
|
||||
|
||||
SI should be always used as Input to avoid problems with other hardware which does not expect data to be output there.
|
||||
|
||||
**4000128h - SIOCNT - SIO Control, not used in GENERAL-PURPOSE Mode**
|
||||
This register is not used in general purpose mode. That is, the separate bits of SIOCNT still exist and are read- and/or write-able in the same manner as for Normal, Multiplay, or UART mode (depending on SIOCNT Bit 12,13), but are having no effect on data being output to the link port.
|
||||
|
||||
This register is not used in general purpose mode. That is, the separate bits of SIOCNT still exist and are read- and/or write-able in the same manner as for Normal, Multiplay, or UART mode (depending on SIOCNT Bit 12,13), but are having no effect on data being output to the link port.
|
||||
|
||||
# SIO Control Registers Summary
|
||||
|
||||
**Mode Selection (by RCNT.15-14 and SIOCNT.13-12)**
|
||||
|
||||
**Mode Selection (by RCNT.15-14 and SIOCNT.13-12)**
|
||||
|
||||
```
|
||||
R.15 R.14 S.13 S.12 Mode
|
||||
|
|
@ -276,8 +276,8 @@ This register is not used in general purpose mode. That is, the separate bits of
|
|||
1 0 x x General Purpose
|
||||
1 1 x x JOY BUS
|
||||
```
|
||||
|
||||
**SIOCNT**
|
||||
|
||||
**SIOCNT**
|
||||
|
||||
```
|
||||
Bit 0 1 2 3 4 5 6 7 8 9 10 11
|
||||
|
|
@ -287,16 +287,17 @@ This register is not used in general purpose mode. That is, the separate bits of
|
|||
```
|
||||
|
||||
# GBA Wireless Adapter
|
||||
|
||||
**GBA Wireless Adapter (AGB-015 or OXY-004)**
|
||||
|
||||
**GBA Wireless Adapter (AGB-015 or OXY-004)**
|
||||
|
||||
- GBA Wireless Adapter Games
|
||||
- GBA Wireless Adapter Login
|
||||
- GBA Wireless Adapter Commands
|
||||
- GBA Wireless Adapter Component Lists
|
||||
|
||||
|
||||
## GBA Wireless Adapter Games
|
||||
|
||||
**GBA Wireless Adapter compatible Games**
|
||||
**GBA Wireless Adapter compatible Games**
|
||||
|
||||
```
|
||||
bit Generations series (Japan only)
|
||||
|
|
@ -341,11 +342,11 @@ This register is not used in general purpose mode. That is, the separate bits of
|
|||
Sennen Kazoku (Japan only)
|
||||
Shrek SuperSlam
|
||||
Sonic Advance 3
|
||||
```
|
||||
```
|
||||
|
||||
## GBA Wireless Adapter Login
|
||||
|
||||
**GBA Wireless Adapter Login**
|
||||
**GBA Wireless Adapter Login**
|
||||
|
||||
```
|
||||
rcnt=8000h ;\\
|
||||
|
|
@ -373,8 +374,8 @@ This register is not used in general purpose mode. That is, the separate bits of
|
|||
ret
|
||||
@@key\_string db 'NINTENDO',01h,80h ;10 bytes (5 halfwords; index=0..4)
|
||||
```
|
||||
|
||||
**Data exchanged during Login**
|
||||
|
||||
**Data exchanged during Login**
|
||||
|
||||
```
|
||||
GBA ADAPTER
|
||||
|
|
@ -394,11 +395,11 @@ This register is not used in general purpose mode. That is, the separate bits of
|
|||
\\ \\
|
||||
MSBs=Inverse of MSBs=Own
|
||||
Prev.Data.From.Adapter Data.From.Adapter
|
||||
```
|
||||
```
|
||||
|
||||
# GBA Wireless Adapter Commands
|
||||
|
||||
**Wireless Command/Parameter Transmission**
|
||||
|
||||
**Wireless Command/Parameter Transmission**
|
||||
|
||||
```
|
||||
GBA Adapter
|
||||
|
|
@ -412,7 +413,7 @@ This register is not used in general purpose mode. That is, the separate bits of
|
|||
... ... ;/
|
||||
```
|
||||
|
||||
Wireless 32bit Transfers
|
||||
Wireless 32bit Transfers
|
||||
|
||||
```
|
||||
wait until \[4000128h\].Bit2=0 ;want SI=0
|
||||
|
|
@ -421,9 +422,9 @@ Wireless 32bit Transfers
|
|||
set \[4000128h\].Bit3=0,Bit7=1 ;set SO=0 and start 32bit transfer
|
||||
```
|
||||
|
||||
All command/param/reply transfers should be done at Internal Clock (except, Response Words for command 25h,27h,35h,37h should use External Clock).
|
||||
|
||||
**Wireless Commands**
|
||||
All command/param/reply transfers should be done at Internal Clock (except, Response Words for command 25h,27h,35h,37h should use External Clock).
|
||||
|
||||
**Wireless Commands**
|
||||
|
||||
```
|
||||
Cmd Para Reply Name
|
||||
|
|
@ -444,7 +445,7 @@ All command/param/reply transfers should be done at Internal Clock (except, Resp
|
|||
1Eh - NN Get Directory? (receive list of game/user names?)
|
||||
1Fh 1 - Select Game for Download (send 16bit Game\_ID)
|
||||
|
||||
|
||||
|
||||
|
||||
20h - 1
|
||||
21h - 1 Good/Bad response to cmd 1Fh ?
|
||||
|
|
@ -463,7 +464,7 @@ All command/param/reply transfers should be done at Internal Clock (except, Resp
|
|||
2Eh
|
||||
2Fh
|
||||
|
||||
|
||||
|
||||
|
||||
30h 1 -
|
||||
31h
|
||||
|
|
@ -483,11 +484,11 @@ All command/param/reply transfers should be done at Internal Clock (except, Resp
|
|||
3Fh
|
||||
```
|
||||
|
||||
Special Response 996601EEh for error or so? (only at software side?)
|
||||
|
||||
Special Response 996601EEh for error or so? (only at software side?)
|
||||
|
||||
## GBA Wireless Adapter Component Lists
|
||||
|
||||
Main Chipset
|
||||
|
||||
Main Chipset
|
||||
|
||||
```
|
||||
U1 32pin Freescale MC13190 (2.4 GHz ISM band transceiver)
|
||||
|
|
@ -497,9 +498,9 @@ Main Chipset
|
|||
|
||||
The MC13190 is a Short-Range, Low-Power 2.4 GHz ISM band transceiver.
|
||||
The processor is Motorola's 32-bit M-Core RISC engine. (?) MCT3000 (?)
|
||||
See also: [http://www.eetimes.com/document.asp?doc\_id=1271943](http://www.eetimes.com/document.asp?doc_id=1271943)
|
||||
|
||||
Version with GERMAN Postal Code on sticker:
|
||||
See also: [http://www.eetimes.com/document.asp?doc_id=1271943](http://www.eetimes.com/document.asp?doc_id=1271943)
|
||||
|
||||
Version with GERMAN Postal Code on sticker:
|
||||
|
||||
```
|
||||
Sticker on Case:
|
||||
|
|
@ -514,7 +515,7 @@ Version with GERMAN Postal Code on sticker:
|
|||
X3 2pin "D959L4I" (9.5MHz) (top side) (ca. 19 clks per 2us)
|
||||
```
|
||||
|
||||
Further components... top side (A-7)
|
||||
Further components... top side (A-7)
|
||||
|
||||
```
|
||||
D1 5pin "D6F, 44" (top side, below X3)
|
||||
|
|
@ -531,7 +532,7 @@ Further components... top side (A-7)
|
|||
CN1 6pin connector to GBA link port (top side)
|
||||
```
|
||||
|
||||
Further components... bottom side (B-7)
|
||||
Further components... bottom side (B-7)
|
||||
|
||||
```
|
||||
U201 5pin "LXVB" (bottom side, near CN1)
|
||||
|
|
@ -540,8 +541,8 @@ Further components... bottom side (B-7)
|
|||
B70 6pin "\[\]" (bottom side, near ANT, small white chip)
|
||||
```
|
||||
|
||||
Plus, resistors and capacitors (without any markings).
|
||||
|
||||
Plus, resistors and capacitors (without any markings).
|
||||
|
||||
Version WITHOUT sticker:
|
||||
|
||||
```
|
||||
|
|
@ -554,7 +555,7 @@ Version WITHOUT sticker:
|
|||
X3 2pin "9.5SKSS4GT" (top side)
|
||||
```
|
||||
|
||||
Further components... top side (A-1)
|
||||
Further components... top side (A-1)
|
||||
|
||||
```
|
||||
D1 5pin "D6F, 31" (top side, below X3)
|
||||
|
|
@ -570,7 +571,7 @@ Further components... top side (A-1)
|
|||
CN1 6pin connector to GBA link port (top side)
|
||||
```
|
||||
|
||||
Further components... bottom side (B-1)
|
||||
Further components... bottom side (B-1)
|
||||
|
||||
```
|
||||
U201 5pin "LXV2" (bottom side, near CN1)
|
||||
|
|
@ -579,9 +580,9 @@ Further components... bottom side (B-1)
|
|||
B70 6pin "\[\]" (bottom side, near ANT, small white chip)
|
||||
```
|
||||
|
||||
Plus, resistors and capacitors (without any markings).
|
||||
|
||||
Major Differences
|
||||
Plus, resistors and capacitors (without any markings).
|
||||
|
||||
Major Differences
|
||||
|
||||
```
|
||||
Sticker "N/A" vs "Grossosteim P/AGB-A-WA-EUR-2 E3"
|
||||
|
|
@ -591,4 +592,4 @@ Major Differences
|
|||
U70/U72 U70 "AAG" (6pin) vs U72 "BMs" (4pin)
|
||||
```
|
||||
|
||||
Purpose of the changes is unknown (either older/newer revisions, or different regions with different FCC regulations).
|
||||
Purpose of the changes is unknown (either older/newer revisions, or different regions with different FCC regulations).
|
||||
|
|
|
|||
BIN
docs/img/link-cable-multiboot.gif
Normal file
|
After Width: | Height: | Size: 703 KiB |
BIN
docs/img/link-cable.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
docs/img/link-cube.gif
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
docs/img/link-gpio.gif
Normal file
|
After Width: | Height: | Size: 812 KiB |
BIN
docs/img/link-mobile.gif
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
docs/img/link-raw-cable.gif
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
docs/img/link-raw-wireless.gif
Normal file
|
After Width: | Height: | Size: 593 KiB |
BIN
docs/img/link-spi.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/img/link-uart.gif
Normal file
|
After Width: | Height: | Size: 906 KiB |
BIN
docs/img/pinout-raspberry-pi.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
docs/img/pinout-uart.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
docs/img/pinout.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
454
docs/mobile_adapter.md
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
🌎 From: https://shonumi.github.io/dandocs.html#magb 🌎
|
||||
|
||||
# Mobile Adapter GB
|
||||
|
||||
- [General Hardware Information](#mobile-adapter-gb--general-hardware-information)
|
||||
- [Compatible Games](#mobile-adapter-gb--compatible-games)
|
||||
- [Protocol - Packet Format](#mobile-adapter-gb--protocol---packet-format)
|
||||
- [Protocol - Flow of Communication](#mobile-adapter-gb--protocol---flow-of-communication)
|
||||
- [Protocol - Commands](#mobile-adapter-gb--protocol---commands)
|
||||
- [Configuration Data](#mobile-adapter-gb--protocol---configuration-data)
|
||||
|
||||
## \[Mobile Adapter GB\] : General Hardware Information
|
||||
|
||||
The Mobile Adapter GB was an accessory designed to allow the Game Boy Color, and later the Game Boy Advance, to connect online via cellular networks in Japan. Released on January 27, 2001, it supported a limited number of games before service was shutdown on December 14, 2002. Many of the compatible games supported features such on mail clients, downloadable bonus content, player-versus-player modes, and even online tournaments. It represented Nintendo's first official attempt at online gaming for its handhelds.
|
||||
|
||||
* The Mobile Adapter is a small device that essentially allows a Japanese phone to connect to the Game Boy's link port
|
||||
* Model number is CGB-005
|
||||
* Officially released with 3 different versions of the Mobile Adapter. Each featured distinct colors to work with different type of phones
|
||||
* Each Mobile Adapter came packaged with a cartridge called the Mobile Trainer to help configure and setup the device
|
||||
* Servers were formally hosted at gameboy.datacenter.ne.jp
|
||||
|
||||
Below, the Mobile Adapter variants are explained in further detail:
|
||||
|
||||
Blue -> Used to connect PDC phones.
|
||||
Yellow -> Used to connect cdmaOne phones.
|
||||
Red -> Used to connect DDI phones.
|
||||
Green -> Would have been used to connect PHS phones, but this version was never released.
|
||||
|
||||
## \[Mobile Adapter GB\] : Compatible Games
|
||||
|
||||
There are currently 22 known games that are compatible with the Mobile Adapter:
|
||||
|
||||
Game Boy Color : 6 Total
|
||||
|
||||
* Game Boy Wars 3
|
||||
* Hello Kitty: Happy House
|
||||
* Mobile Golf
|
||||
* Mobile Trainer
|
||||
* Net de Get Minigames @ 100
|
||||
* Pocket Monsters Crystal Version
|
||||
|
||||
Game Boy Advance : 16 Total
|
||||
|
||||
* All-Japan GT Championship
|
||||
* Daisenryaku For Game Boy Advance
|
||||
* Doraemon: Midori no Wakusei Doki Doki Daikyuushuutsu!
|
||||
* Exciting Bass
|
||||
* EX Monopoly
|
||||
* JGTO Licensed: Golfmaster Mobile
|
||||
* Kinniku Banzuke ~Kongou-kun no Daibouken!~
|
||||
* Mail de Cute
|
||||
* Mario Kart Advance
|
||||
* Mobile Pro Baseball: Control Baton
|
||||
* Monster Guardians
|
||||
* Morita Shougi Advance
|
||||
* Napoleon
|
||||
* Play Novel: Silent Hill
|
||||
* Starcom: Star Communicator
|
||||
* Zero-Tours
|
||||
|
||||
Two games were planned but later cancelled: **beatmaniaGB Net Jam** for the GBC and **Horse Racing Creating Derby** for the GBA.
|
||||
|
||||
The GBA game Yu-Gi-Oh! Duel Monsters 5 Expert 1 contains code for the Mobile Adapter, but despite being built with the library it does not appear to use it. This functionality may have been planned and later abandoned.
|
||||
|
||||
## \[Mobile Adapter GB\] : Protocol - Packet Format
|
||||
|
||||
On the GBC, the Mobile Adapter operates using the fastest available setting (64KB/s) by setting Bits 0 and 1 of the SC register (0xFF02) high. It also uses an internal clock for all transfers. Communication is comparable to that of the Game Boy Printer, where the Game Boy sends packets with header, data, command, and checksum sections. On the GBA, the Mobile Adapter operates in NORMAL8 mode using a shift clock of 256KHz. Below is a chart breaking down the Mobile Adapter packet format used by the Game Boy or Mobile Adapter when acting as the sender. For response data sent by the receiver, refer to the next section.
|
||||
|
||||
```
|
||||
-------------------------------------------------
|
||||
Section | Length
|
||||
-------------------------------------------------
|
||||
Magic Bytes : 0x99 0x66 | 2 bytes
|
||||
Packet Header | 4 bytes
|
||||
Packet Data | 0-254 bytes
|
||||
Packet Checksum | 2 bytes
|
||||
Acknowledgement Signal | 2 bytes
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
Packet Header
|
||||
-------------------------------------------------
|
||||
Byte 1 | Command ID
|
||||
Byte 2 | Unknown/Unused (0x00)
|
||||
Byte 3 | High byte of Packet Data length
|
||||
Byte 4 | Low byte of Packet Data length
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
Packet Data
|
||||
-------------------------------------------------
|
||||
Bytes 0-254 | Arbitrary data
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
Packet Checksum
|
||||
-------------------------------------------------
|
||||
Byte 1 | High byte of 16-bit sum
|
||||
Byte 2 | Low byte of 16-bit sum
|
||||
-------------------------------------------------
|
||||
|
||||
|
||||
-------------------------------------------------
|
||||
Acknowledgement Signal
|
||||
-------------------------------------------------
|
||||
Byte 1 | Device ID
|
||||
Byte 2 | Command ID
|
||||
-------------------------------------------------
|
||||
```
|
||||
|
||||
The magic bytes are simply a pair of bytes used to identify the start of a Mobile Adapter packet.
|
||||
|
||||
Packet Data is arbitrary data and varies in length and content. On the Game Boy Color, it has a maximum size of 254 bytes. This restriction may be applied via software and appears to come from the fact that the Packet Data and Packet Checksum are lumped together, thus their total lengths must not exceed 256 bytes. Attempting to send more than 254 bytes of packet data causes communications errors in all supported GBC games. Evidence suggests GBA games can use Bytes 3 and 4 of the Packet Header to specify Packet Data size (possibly up to 64KB). The Mobile Adapter discards any packets bigger than 255 bytes, effectively forcing the high byte of the packet data length to be 0.
|
||||
|
||||
Data greater than the maximum packet length may be broken up into multiple packets, however. For example, when sending a large binary file such as an image or executable code, multiple packets are transferred from the Mobile Adapter to the Game Boy while the TCP transfer is ongoing.
|
||||
|
||||
The Packet Checksum is simply the 16-bit sum of all previous header bytes and all previous packet data bytes. It does not include the magic bytes. The checksum is transmitted big-endian.
|
||||
|
||||
After the checksum, a simple 2-byte Acknowledgement Signal is sent. The first byte is the Device ID OR'ed with the value 0x80. The second byte is 0x00 for the sender. The receiver transfers the Command ID from the Packet Header XOR'ed by 0x80. This essentially confirms what role the Game Boy is acting in. If it is the receiver, it is expecting to read information from the Packet Data from the Mobile Adapter. If it is the sender, it is pushing information from its own Packet Data to the Mobile Adapter. For example, with Command 0x19, the Game Boy is explicitly requesting data from the adapter, and with Command 0x1A the Game Boy is explicitly sending data to the adapter.
|
||||
|
||||
The Command ID byte in the Acknowledgement Signal may also be used for the receiver to indicate an error. If the checksum verification fails, the receiving side will send error 0xF1. This causes the sender to immediately re-attempt sending the packet up to 4 times. If the command isn't implemented/supported by the receiving Mobile Adapter, error 0xF0 will be sent. Error 0xF2 indicates an internal error, such as the Mobile Adapter's TCP/telephone transfer buffer being full.
|
||||
|
||||
The device ID determines what kind of hardware each side is communicating with. Below are the possible values and their meaning:
|
||||
|
||||
```
|
||||
-------------------------------------------------
|
||||
Device ID | OR Value | Device Type
|
||||
-------------------------------------------------
|
||||
0x00 | 0x80 | Game Boy Color
|
||||
0x01 | 0x81 | Game Boy Advance
|
||||
0x08 | 0x88 | PDC Mobile Adapter (Blue)
|
||||
0x09 | 0x89 | cdmaOne Mobile Adapter (Yellow)
|
||||
0x0A | 0x8A | PHS Mobile Adapter (Green)
|
||||
0x0B | 0x8B | DDI Mobile Adapter (Red)
|
||||
-------------------------------------------------
|
||||
```
|
||||
|
||||
## \[Mobile Adapter GB\] : Protocol - Flow of Communication
|
||||
|
||||
Even though the protocol effectively enables 2-way communication between the Game Boy and a remote server, the handheld is expected to oversee all transmissions to the adapter itself. That is to say, the typical "master-slave" model often used for Game Boy serial I/O still applies in some sense. Once the server starts responding, the Game Boy has to continually initiate another transfer to the adapter (setting Bit 7 of 0xFF02 high) to keep reading any additional bytes that were sent.
|
||||
|
||||
It is up to the game software itself to handle secondary protocols (such as HTTP, POP3, or SMTP) which involve one side specifically acting as the sender or receiver. For example, after opening a TCP connection to an HTTP server and issuing the 0x15 command (Data Transfer), the software will determine whether the Game Boy is acting as a sender (making an HTTP request) or a receiver (receiving an HTTP response). Generally, this goes back and forth. The Game Boy sends information via its Packet Data, while the Mobile Adapter responds with 0xD2 "wait" bytes until the Game Boy finishes its TCP transfer. When the Game Boy's TCP transfer is done, the adapter sends any information from the server in its Packet Data while the Game Boy responds with 0x4B "wait" bytes. The chart below illustrates this concept and details what bytes are transferred by each side depending on their current role:
|
||||
|
||||
Device | Role | Magic Bytes | Packet Header | Packet Checksum | Packet Data | Acknowledgement Signal
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
Game Boy | Sender | 0x96 0x66 | Arbitrary | Arbitrary | Arbitrary | Device ID OR 0x80 + 0x00
|
||||
Mobile Adapter | Receiver | 0xD2 0xD2 | 0xD2 0xD2 ... | 0xD2 0xD2 ... ... ... | 0xD2 0xD2 ... | Device ID OR 0x80 + Command ID XOR 0x80
|
||||
--- | --- | --- | --- | --- | --- | ---
|
||||
Game Boy | Receiver | 0x4B 0x4B | 0x4B 0x4B ... | 0x4B 0x4B ... ... ... | 0x4B 0x4B ... | Device ID OR 0x80 + Command ID XOR 0x80
|
||||
Mobile Adapter | Sender | 0x96 0x66 | Arbitrary | Arbitrary | Arbitrary | Device ID OR 0x80 + 0x00
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
When beginning communications with the Mobile Adapter, the Game Boy typically assumes the role of sender first.
|
||||
|
||||
Many games appear to follow a certain order of commands initially. This may have been part of some kind of standard library available to developers in order to connect to an ISP. The commands most commonly look like this:
|
||||
|
||||
```
|
||||
------------
|
||||
Command 0x10 Begin Session. First is perhaps to test the presence of the Mobile Adapter
|
||||
Command 0x11 Close Session.
|
||||
Command 0x10 Begin Session. Open session for configuration data
|
||||
------------
|
||||
Command 0x19 Read Configuration Data. Grab first 96 bytes
|
||||
Command 0x19 Read Configuration Data. Grab second 96 bytes
|
||||
Command 0x11 Close Session.
|
||||
Command 0x10 Begin Session. Open session to read configuration data again
|
||||
Command 0x19 Read Configuration Data. Grab first 96 bytes
|
||||
Command 0x19 Read Configuration Data. Grab second 96 bytes
|
||||
------------
|
||||
Command 0x17 Check Telephone Status if not busy
|
||||
Command 0x12 Dial Telephone. Should be the ISP's number stored in configuration data
|
||||
Command 0x21 ISP Login
|
||||
Command 0x28 DNS Query
|
||||
------------
|
||||
```
|
||||
|
||||
From there, the software decides what next (if anything) needs to be done after successfully connecting to the internet.
|
||||
|
||||
When the GBC or GBA first start communicating with the Mobile Adapter, the first byte sent in response will be garbage data. Sending this first byte causes the Mobile Adapter to exit sleep mode, and the GBC or GBA will then have to wait a short interval (around 100ms) before starting communications proper. If it doesn't, the Mobile Adapter might send more garbage. Afterwards, however, it will reply with 0xD2 as its "idle" byte until a command is finished being sent. The Mobile Adapter enters sleep mode after 3 seconds since the last serial byte was transmitted. This implicitly cancels the command currently being processed, closes all connections currently open and ends the session.
|
||||
|
||||
## \[Mobile Adapter GB\] : Protocol - Commands
|
||||
|
||||
**Command 0x0F - Empty**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Doesn't incite a reply from the adapter at all, aside from the Acknowledgement Signal. Presumably used to ping the adapter, not seen in any games in the wild.
|
||||
|
||||
**Command 0x10 - Begin Session**
|
||||
|
||||
Data Sent: "NINTENDO" ASCII string. 8 bytes only, not null-terminated
|
||||
|
||||
Data Received: "NINTENDO" ASCII string. 8 bytes only, not null-terminated
|
||||
|
||||
Sent to the adapter at the beginning of a session. The Game Boy sends an ASCII string containing "NINTENDO" and the adapter replies with a packet containing the same data. It must be noted that the adapter will not respond to other commands until it receives this command. If this command is sent twice, it returns an error.
|
||||
|
||||
**Command 0x11 - End Session**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
Sent to the adapter at the end of a session. The Packet Data is empty, and the length is zero bytes. This command causes all connections to be closed, and the phone to be hung up.
|
||||
|
||||
**Command 0x12 - Dial Telephone**
|
||||
|
||||
Data Sent: 1 unknown byte + telephone number
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
Instructs the adapter to dial a telephone number. The first byte's purpose is unknown, but seems to vary depending on the adapter type. 0 is sent for the blue/PDC adapter, 1 is sent for the green/PHS or red/DDI adapters, and 2 for the yellow/cdmaOne adapter. For unknown reasons, the blue adapter also accepts 16, the red adapter also accepts 9, and the yellow adapter doesn't actually verify this value. The following data is the telephone number represented in ASCII values, consisting of decimal numbers "0" through "9", as well as "#" and "\*". Any ASCII values not within this range are ignored. The maximum length of the phone number is 32 bytes.
|
||||
|
||||
**Command 0x13 - Hang Up Telephone**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
Instructs the adapter to close a telephone connection. This implicitly disconnects any open TCP/UDP connections. The Packet Data is empty, and the length is zero bytes.
|
||||
|
||||
**Command 0x14 - Wait For Telephone Call**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
Instructs the adapter to wait for and pick up an incoming call. This returns inmediately if there is no call to pick up, with an error packet with code 0. The Packet Data is empty, and the length is zero bytes.
|
||||
|
||||
**Command 0x15 - Transfer Data**
|
||||
|
||||
Data Sent: Connection ID + Arbitrary Data (optional)
|
||||
|
||||
Data Received: Connection ID + Arbitrary Data (optional)
|
||||
|
||||
Used to transfer data over TCP after command 0x23 (Open TCP Connection), transfer data over UDP after command 0x25 (Open UDP connection), or transfer data over the phone line after either 0x12 (Dial Telephone) or 0x14 (Wait For Telephone Call) have successfully been called. Only TCP/UDP communication is possible after a 0x21 (ISP Login) command, and the first byte indicates the connection that's being transferred over, as multiple can be opened simultaneously. If it's a mobile connection, the first byte is ignored, usually being set to 0xFF.
|
||||
|
||||
Generally, additional data is appended, although it is not required, such as when waiting for the server/other phone to send a reply. Large chunks of data greater than 254 bytes must be broken down into separate packets. While a connection is active, the Command ID in the Reply is 0x15 for the sender and 0x95 for the receiver. When a TCP connection is closed by the remote server (e.g. when an HTTP response has finished), and there's no leftover data to be received by the Game Boy, the Command ID in the Reply becomes 0x1F for the sender and 0x9F for the receiver, with a packet length of 0. Additionally, for TCP connections, if no data is sent, this command will wait for data to be received up to 1 second, before sending a reply.
|
||||
|
||||
During a phone-to-phone communication, no disconnection is detected, instead being indicated by the 0x17 (Telephone Status) command. However, most games implement this instead through a timeout during which no data has been received.
|
||||
|
||||
**Command 0x16 - Reset**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
This command does the same as sending commands 0x11 (End Session), followed by 0x10 (Begin Session). Additionally, it resets SIO32 Mode to the default state. Presumably used to reset the adapter, though not seen in any games in the wild.
|
||||
|
||||
**Command 0x17 - Telephone Status**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Data Received: 3 bytes
|
||||
|
||||
Typically sent to the adapter before dialing. Also used to test the telephone status before opening a TCP connection, or to constantly ping the phone to make sure it's still active.
|
||||
|
||||
The reply is 3 bytes. The first byte indicates the phone's status, where 0xFF is returned if the phone is disconnected. If the phone isn't disconnected, bit 2 indicates whether the phone line is "busy" (i.e. in a call/picked up), and bit 0 indicates the presence of an incoming call (this remains if the incoming call is picked up). As such, the valid values are 0, 1, 4 and 5. Software may check bit 2 to know if the phone line is still connected. Most software doesn't seem to care about bit 0, but Net de Get: Mini Game @ 100 refuses to work with bit 0 set (value 5).
|
||||
|
||||
The second byte is related to the adapter type, where the blue/PDC adapter returns 0x4D, and the red/DDI and yellow/cdmaOne adapters return 0x48, though the actual meaning is unknown. The third byte is unknown, and usually hardcoded to 0. However, Pokemon Crystal reacts to the third byte being 0xF0 by allowing the player to bypass the 10 min/day battle time limit.
|
||||
|
||||
**Command 0x18 - SIO32 Mode**
|
||||
|
||||
Data Sent: 1 byte
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
This command is generally sent after Command 0x10. It enables/disables SIO32 Mode, which is useful for GBA games to be able to send more data, faster. The sent byte must be 1 to enable SIO32 mode, and 0 to disable it. SIO32 mode implies that any transmission will happen in chunks of 4 bytes instead of 1, which has implications with respect to the alignment of the communication.
|
||||
|
||||
When SIO32 mode is on, the packet data will be aligned to a multiple of 4 bytes, padding the remaining bytes with 0, and this won't be reflected in the packet length field. Similarly, the Acknowledgement Signal gains 2 padding bytes (hardcoded 0, not verified) at the end. Since the entire transmission (including Magic Bytes) is sent in chunks of 4, this means that the checksum is sent along with either the packet length (if length is 0) or the last 2 bytes of the packet (if length is not 0), and the acknowledgement signal is sent in the next chunk.
|
||||
|
||||
SIO32 Mode will only be toggled after the reply to this packet has been sent. The adapter should be allowed at least 100ms to toggle, as it might otherwise start sending garbage.
|
||||
|
||||
**Command 0x19 - Read Configuration Data**
|
||||
|
||||
Data Sent: 1 byte offset + 1 byte read length
|
||||
|
||||
Data Received: 1 byte offset + Requested Configuration Data
|
||||
|
||||
Requests data from the adapter's 256-byte configuration memory. The first byte sent to the adapter is the offset. The second byte sent is the length of data to read. The adapter responds with the same offset byte followed by configuration data from the adapter's internal memory. The maximum amount of data that can be requested at once is 128 bytes, and the adapter may return an error if the game requests more data. Most software send 2 of these commands to read 96-byte chunks, for a total of 192 bytes, which is the area of this memory that is actually used.
|
||||
|
||||
**Command 0x1A - Write Configuration Data**
|
||||
|
||||
Data Sent: 1 byte offset + Configuration Data to Write
|
||||
|
||||
Data Received: 1 byte offset
|
||||
|
||||
Writes data to the adapter's 256-byte configuration memory. The first byte sent to the adapter is the offset. The following bytes are the data to be written in the adapters internal memory. A maximum of 128 bytes may be written at once.
|
||||
|
||||
**Command 0x21 - ISP Login**
|
||||
|
||||
Data Sent: 1 byte Login ID Length + Login ID + 1 byte Password Length + Password + 4 bytes DNS Address #1 + 4 bytes DNS Address #2
|
||||
|
||||
Data Received: 4 bytes assigned IP + 4 bytes assigned DNS Address #1 + 4 bytes assigned DNS Address #2 /p>
|
||||
|
||||
Logs into the DION dial-up service, after calling it with command 0x12 (Dial Telephone), allowing the adapter to connect to the internet. Both the Login ID and Password are prefixed with bytes declaring their lengths, with a maximum length of 0x20. The IPv4 DNS addresses are 4 bytes each, with a single byte representing one octet. The reply contains the assigned IP address and DNS addresses. If the game sets either of the DNS addresses to 0, the adapter may assign the DNS address on its own, and return that in the reply, otherwise, the reply's DNS addresses are 0.0.0.0.
|
||||
|
||||
**Command 0x22 - ISP Logout**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
Logs out of the DION service. This command causes all connections to be closed.
|
||||
|
||||
**Command 0x23 - Open TCP Connection**
|
||||
|
||||
Data Sent: 4 bytes for IP Address + 2 Bytes for Port Number
|
||||
|
||||
Data Received: 1 byte Connection ID
|
||||
|
||||
Opens a TCP connection at the given IP address on the given port, after logging into the DION dial-up service. The IPv4 IP address is 4 bytes, with a single byte representing one octet. The port number is big-endian. Depending on which port the TCP connection opens (25, 80, 110), different protocols can be accessed on a server (SMTP, HTTP, and POP respectively). Handling the details of the protocol itself depends on the software and the server. The Mobile Adapter is merely responsible for opening the connection and handling TCP transfers such as when using Command 0x15. The reply contains the Connection ID, which must be used when using Command 0x15 (Transfer Data). The maximum amount of connections on a real adapter is 2.
|
||||
|
||||
**Command 0x24 - Close TCP Connection**
|
||||
|
||||
Data Sent: 1 byte Connection ID
|
||||
|
||||
Data Received: 1 byte Connection ID
|
||||
|
||||
Closes an active TCP connection.
|
||||
|
||||
**Command 0x25 - Open UDP Connection**
|
||||
|
||||
Data Sent: 4 bytes for IP Address + 2 Bytes for Port Number
|
||||
|
||||
Data Received: 1 byte Connection ID
|
||||
|
||||
Opens a UDP connection at the given IP address on the given port, after logging into the DION dial-up service. It's an analog of Command 0x23 (Open TCP Connection), but opens a UDP connection instead. This UDP connection is bound to the specified IP address and Port until it's closed. When using Command 0x15 (Transfer Data) with a UDP connection, it's impossible to know the sender of any received data, as it isn't verified.
|
||||
|
||||
**Command 0x26 - Close UDP Connection**
|
||||
|
||||
Data Sent: 1 byte Connection ID
|
||||
|
||||
Data Received: 1 byte Connection ID
|
||||
|
||||
Closes an active UDP connection.
|
||||
|
||||
**Command 0x28 - DNS Query**
|
||||
|
||||
Data Sent: Domain Name
|
||||
|
||||
Data Received: 4 bytes for IP Address
|
||||
|
||||
Looks up the IP address for a domain name, using the DNS server addresses sent in Command 0x21. This command also accepts an ASCII IPv4 address (as parsed by the inet\_addr(3) function of POSIX), converting it into a 4-byte IPv4 address instead of querying the DNS server. The domain name is in ASCII and may contain zeroes, which truncate the name.
|
||||
|
||||
**Command 0x3F - Firmware Version**
|
||||
|
||||
Data Sent: N/A. Empty Packet Data
|
||||
|
||||
Data Received: N/A. Empty Packet Data
|
||||
|
||||
On a real Mobile Adapter, this causes it to send firmware version information through the serial pins on the phone connector, and enter a state in which no other commands can be used. Presumably, this enters a test mode of some kind. Likely not used by any games.
|
||||
|
||||
This command may not be used if the phone line is in use. The only way to resume sending commands after this one is sent, is sending Command 0x16 (Reset), or, exclusively on the blue adapter, Command 0x11 (End Session) may also be used.
|
||||
|
||||
**Command 0x6E - Error Status**
|
||||
|
||||
Data Sent: N/A. Adapter sends this in response to a failed command
|
||||
|
||||
Data Received: 1 byte for command that failed + 1 byte for error status
|
||||
|
||||
If a previously sent command fails, the adapter will respond with this instead, indicating the command that failed as well as a brief status code. The error statuses for one command do not indicate the same error for another command, so context matters when parsing the codes. The following commands and their known error status codes are listed below:
|
||||
|
||||
```
|
||||
0x10: Error Code 0x01 - Sent twice
|
||||
0x10: Error Code 0x02 - Invalid contents
|
||||
|
||||
0x11: Error Code 0x02 - Still connected/failed to disconnect(?)
|
||||
|
||||
0x12: Error Code 0x00 - Telephone line is busy
|
||||
0x12: Error Code 0x01 - Invalid use (already connected)
|
||||
0x12: Error Code 0x02 - Invalid contents (first byte isn't correct)
|
||||
0x12: Error Code 0x03 - Communication failed/phone not connected
|
||||
0x12: Error Code 0x04 - Call not established, redial
|
||||
|
||||
0x13: Error Code 0x01 - Invalid use (already hung up/phone not connected)
|
||||
|
||||
0x14: Error Code 0x00 - No call received/phone not connected
|
||||
0x14: Error Code 0x01 - Invalid use (already calling)
|
||||
0x14: Error Code 0x03 - Internal error (ringing but picking up fails)
|
||||
|
||||
0x15: Error Code 0x00 - Invalid connection/communication failed
|
||||
0x15: Error Code 0x01 - Invalid use (Call was ended/never made)
|
||||
|
||||
0x16: Error Code 0x00 - Still connected/failed to disconnect(?)
|
||||
|
||||
0x18: Error Code 0x02 - Invalid contents (first byte not either 1 or 0)
|
||||
|
||||
0x19: Error Code 0x00 - Internal error (Failed to read config)
|
||||
0x19: Error Code 0x02 - Read outside of config area/too big a chunk
|
||||
|
||||
0x1A: Error Code 0x00 - Internal error (Failed to write config)
|
||||
0x1A: Error Code 0x02 - Write outside of config area/too big a chunk
|
||||
|
||||
0x21: Error Code 0x01 - Invalid use (Not in a call)
|
||||
0x21: Error Code 0x02 - Unknown error (some kind of timeout?)
|
||||
0x21: Error Code 0x03 - Unknown error (internal error?)
|
||||
|
||||
0x22: Error Code 0x00 - Invalid use (Not logged in)
|
||||
0x22: Error Code 0x01 - Invalid use (Not in a call)
|
||||
0x22: Error Code 0x02 - Unknown error (some kind of timeout?)
|
||||
|
||||
0x23: Error Code 0x00 - Too many connections
|
||||
0x23: Error Code 0x01 - Invalid use (Not logged in)
|
||||
0x23: Error Code 0x03 - Connection failed
|
||||
|
||||
0x24: Error Code 0x00 - Invalid connection (Not connected)
|
||||
0x24: Error Code 0x01 - Invalid use (Not logged in)
|
||||
0x24: Error Code 0x02 - Unknown error (???)
|
||||
|
||||
0x25: Error Code 0x00 - Too many connections
|
||||
0x25: Error Code 0x01 - Invalid use (Not logged in)
|
||||
0x25: Error Code 0x03 - Connection failed (though this can't happen)
|
||||
|
||||
0x26: Error Code 0x00 - Invalid connection (Not connected)
|
||||
0x26: Error Code 0x01 - Invalid use (Not logged in)
|
||||
0x26: Error Code 0x02 - Unknown error (???)
|
||||
|
||||
0x28: Error Code 0x01 - Invalid use (not logged in)
|
||||
0x28: Error Code 0x02 - Invalid contents/lookup failed
|
||||
```
|
||||
|
||||
## \[Mobile Adapter GB\] : Protocol - Configuration Data
|
||||
|
||||
The Mobile Adapter has small area of built-in memory designed to store various settings for its configuration. It only uses 192 bytes but data is readable and writable via the Commands 0x19 and 0x1A respectively. These fields are filled out when running the initial setup on Mobile Trainer. The memory is laid out as describe below:
|
||||
|
||||
```
|
||||
--------------------------
|
||||
0x00 - 0x01 :: "MA" in ASCII. The "Mobile Adapter" header.
|
||||
0x02 :: Set to 0x1 during Mobile Trainer registration and 0x81 when registration is complete
|
||||
0x04 - 0x07 :: Primary DNS server (210.196.3.183)
|
||||
0x08 - 0x0B :: Secondary DNS server (210.141.112.163)
|
||||
0x0C - 0x15 :: Login ID in the format gXXXXXXXXX. Mobile Trainer only allows 9 editable characters
|
||||
0x2C - 0x43 :: User email address in the format XXXXXXXX@YYYY.dion.ne.jp
|
||||
0x4A - 0x5D :: SMTP server in the format mail.XXXX.dion.ne.jp
|
||||
0x5E - 0x70 :: POP server in the format pop.XXXX.dion.ne.jp
|
||||
0x76 - 0x8D :: Configuration Slot #1
|
||||
0x8E - 0xA5 :: Configuration Slot #2
|
||||
0xA6 - 0xBD :: Configuration Slot #3
|
||||
0xBE - 0xBF :: 16-bit big-endian checksum
|
||||
--------------------------
|
||||
```
|
||||
|
||||
Each configuration slot may contain an 8-byte telephone number to be used to connect to the ISP and a 16-byte ID string. The telephone number is stored in a variant of binary-coded decimal, where 0x0A represents the "#" key, 0x0B represents the "\*" key, and 0x0F marks the end of the telephone number. These slots may have been intended to allow users to connect online using ISPs besides DION at some point, however, Nintendo never implemented any such plans.
|
||||
|
||||
If the Mobile Adapter is connected to a PDC or CDMA device, the telephone number defaults to #9677 with an ID string of "DION PDC/CDMAONE". If the Mobile Adapter is connected to a PHS or DDI device, the telephone number defaults to 0077487751 with an ID string of "DION DDI-POCKET". Only the first slot is configured by Mobile Trainer; it fills the rest with 0xFF and 0x00 bytes. An unidentified device (as reported by the Device ID in the Acknowledgement Signal of a packet) causes the Mobile Adapter to overwrite all configuration data with garbage values.
|
||||
|
||||
The checksum is simply the 16-bit sum of bytes 0x00 - 0xBD.
|
||||
|
||||
All software compatible with the Mobile Adapter appears to read the configuration data first and foremost. If the data cannot be read or if there is a problem with the data, they will refuse to even attempt logging in to the DION dial-up service. Generally, they return the error code 25-000 in that situation.
|
||||
|
||||
If any compatible software attempts to read or write configuration data outside the allotted 256 bytes via commands 0x19 and 0x1A, the entire I/O operation is cancelled. No data is written even if the initial offset is within the 256 bytes. No data is returned either, as both commands respond with Error Status packets.
|
||||
|
|
@ -137,4 +137,4 @@
|
|||
<li>MultiPlay mode: <a href="https://www.problemkaputt.de/gbatek.htm#siomultiplayermode" rel="nofollow noreferrer">https://www.problemkaputt.de/gbatek.htm#siomultiplayermode</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,50 +1,45 @@
|
|||
Game Boy Advance Wireless Adapter
|
||||
-------------------------------------------------
|
||||
## 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
|
||||
====================
|
||||
# The Wireless Adapter
|
||||
|
||||
[](img/wirelessadapter.jpg)
|
||||
[](img/wireless/wirelessadapter.jpg)
|
||||
|
||||
*The Game Boy Advance Wireless Adapter*
|
||||
_The Game Boy Advance Wireless Adapter_
|
||||
|
||||
The wireless adapter is a piece of hardware that connects to the link cable port of a GBA that then communicates wirelessly with other adapters. It also contains a multibootable[1](#fn:multiboot) rom for playing games only one player has a copy of (although I am not aware of many games that use it, some NES classic games use this). However, the most notable games to use it is probably the Pokémon games Fire Red, Leaf Green and Emerald (Sapphire and Ruby do _not_ have wireless adapter support)[2](#fn:list_of_games).
|
||||
|
||||
[](img/multiboot.jpg)
|
||||
username of CORWIN](img/wireless/multiboot.jpg)](img/wireless/multiboot.jpg)
|
||||
|
||||
*You can make this screen display any game*
|
||||
_You can make this screen display any game_
|
||||
|
||||
Communicating with the adapter
|
||||
==============================
|
||||
# Communicating with the adapter
|
||||
|
||||
When I started, I used the following resources to start being able to talk with the wireless adapter:
|
||||
|
||||
* [This Gist contains some details](wireless.txt)
|
||||
* [GBATEK has a section on the wireless adapter](gbatek.md)
|
||||
- [This Gist contains some details](wireless.txt)
|
||||
- [GBATEK has a section on the wireless adapter](gbatek.md)
|
||||
|
||||
Pinout
|
||||
------
|
||||
## Pinout
|
||||
|
||||
The wireless adapter connects using the link cable port to the GBA. It uses
|
||||
|
||||
* 3.3V
|
||||
* Serial In
|
||||
* Serial out
|
||||
* SD
|
||||
* Clock
|
||||
* Ground
|
||||
- 3.3V
|
||||
- Serial In
|
||||
- Serial out
|
||||
- SD
|
||||
- Clock
|
||||
- Ground
|
||||
|
||||
which is all 6 of the pins. If you are going to mess with interfacing with the link cable yourself, make sure you know which pin is which. If you just want to use the wireless adapter as part of the GBA this isn’t relevant.
|
||||
|
||||
Serial Peripheral Interface
|
||||
---------------------------
|
||||
## Serial Peripheral Interface
|
||||
|
||||
Broadly speaking the GBA communicates with the wireless adapter using the Serial Peripheral Interface (SPI), however it can be somewhat weird. In the case of the GBA this is a three or four wire protocol depending on how you count. The clock, two data wires, and what is normally chip select but operates more as a reset.
|
||||
|
||||
|
|
@ -52,24 +47,23 @@ Broadly speaking the GBA communicates with the wireless adapter using the Serial
|
|||
|
||||
[](img/init.png)
|
||||
communications](img/wireless/init.png)](img/wireless/init.png)
|
||||
|
||||
*A logic analyser can be used to probe the link cable protocol between the GBA and a Wireless Adapter*
|
||||
_A logic analyser can be used to probe the link cable protocol between the GBA and a Wireless Adapter_
|
||||
|
||||
I will break up the ways in which you communicate into three parts:
|
||||
|
||||
* Initialisation
|
||||
* Commands
|
||||
* Waiting for data
|
||||
- Initialisation
|
||||
- Commands
|
||||
- Waiting for data
|
||||
|
||||
One thing to make note of is that when I have screenshots showing the logic analyser traces, these all come from Pokémon Emerald as it is what I had at the time I did a lot of this.
|
||||
|
||||
Initialisation
|
||||
--------------
|
||||
## Initialisation
|
||||
|
||||
[](img/full_initialisation.png)
|
||||
[](img/wireless/full_initialisation.png)
|
||||
|
||||
*The initialisation sequence captured using a logic analyser*
|
||||
_The initialisation sequence captured using a logic analyser_
|
||||
|
||||
Before starting sending and receiving commands, a handshake with the adapter needs to be done. During this, the clocks runs at 256 kHz. Real games start this process by resetting the adapter.
|
||||
|
||||
|
|
@ -84,24 +78,24 @@ Next is the Nintendo Exchange.
|
|||
The GBA and the adapter exchange the word “NINTENDO” with each other in quite a strange way.
|
||||
|
||||
[](img/first_single_u32.png)
|
||||
sends `0x7FFF494E` and wireless adapter sends `0x00000000`.](img/wireless/first_single_u32.png)](img/wireless/first_single_u32.png)
|
||||
|
||||
*GBA sends `0x7FFF494E` and wireless adapter sends `0x00000000`.*
|
||||
_GBA sends `0x7FFF494E` and wireless adapter sends `0x00000000`._
|
||||
|
||||
The GBA here sends `0x7FFF494E`, of this the relevant part is the `0x494E`. If we look up what the bytes `0x49, 0x4E` are you will find them to be the letters `NI`. As exchanges happen simultaneously, at this point the adapter doesn’t know what to respond with and so responds with all zeros.
|
||||
|
||||
[](img/first_nintendo_32.png)
|
||||
[](img/wireless/first_nintendo_32.png)
|
||||
|
||||
*GBA sends `0xFFFF494E` and wireless adapter sends `0x494EB6B1`.*
|
||||
_GBA sends `0xFFFF494E` and wireless adapter sends `0x494EB6B1`._
|
||||
|
||||
Next the GBA sends `0xFFFF494E` and now the wireless adapter does respond and responds with `0x494EB6B1`. I can assure you there is a pattern here:
|
||||
|
||||
* GBA:
|
||||
* Two _most_ significant bytes are the inverse of the adapters previous _most_ significant bytes.
|
||||
* Two _least_ significant bytes are the GBA’s own data.
|
||||
* Adapter:
|
||||
* Two _least_ significant bytes are the inverse of the GBA’s previous _least_ significant bytes.
|
||||
* Two most significant bytes are the adapters own data.
|
||||
- GBA:
|
||||
- Two _most_ significant bytes are the inverse of the adapters previous _most_ significant bytes.
|
||||
- Two _least_ significant bytes are the GBA’s own data.
|
||||
- Adapter:
|
||||
- Two _least_ significant bytes are the inverse of the GBA’s previous _least_ significant bytes.
|
||||
- Two most significant bytes are the adapters own data.
|
||||
|
||||
The “own” data are the bytes of the string “NINTENDO”, and you advance to the next pair when the most significant bytes equal the inverse of the least significant bytes.
|
||||
|
||||
|
|
@ -160,48 +154,48 @@ Following these rules the transfer looks like
|
|||
|
||||
Although note that due to the rules, the first few transfers may contain some junk data and be different to this in practice. And after this, you can start sending commands.
|
||||
|
||||
Commands
|
||||
--------
|
||||
## Commands
|
||||
|
||||
[](img/0x17.png)
|
||||
adapter](img/wireless/0x17.png)](img/wireless/0x17.png)
|
||||
|
||||
*A command being sent by the GBA and acknowledged by the adapter*
|
||||
_A command being sent by the GBA and acknowledged by the adapter_
|
||||
|
||||
Commands are how you tell the adapter to do things. When in command mode the clock operates at 2 mHz. Some examples of commands include connect to adapter, send message, and receive message. All commands follow the same form:
|
||||
|
||||
* Command
|
||||
|
||||
The command is a 32 bit value of the form `0x9966LLCC`:
|
||||
|
||||
* LL
|
||||
* The length of the data payload in number of 32 bit values. For example here it is `0x01`, so one value is transmitted after this.
|
||||
* CC
|
||||
* The command type, there are a bunch of these! In this case the command type is `0x17`.
|
||||
* Data
|
||||
|
||||
All the data along with the command, must transmit the number given in the command
|
||||
|
||||
* Acknowledge
|
||||
|
||||
The adapter responds with a command, the length is the number of 32 bit values and the command type is always what you send + `0x80`. In this case the length is zero and the command is `0x17` + `0x80` = `0x97`.
|
||||
|
||||
* ⚠️ When you send invalid commands or a one you're not supposed to send in the current state (like sending a `0x1d` before a `0x1c`), the adapter responds `0x996601ee`. If you read the next word (as the response size is `01`), it gives you an error code (`2` when using an invalid command, `1` when using a valid command in an invalid state, or `0`).
|
||||
- Command
|
||||
|
||||
* Response
|
||||
|
||||
The data that the adapter responds with. Equal to the length given in the acknowledgement.
|
||||
The command is a 32 bit value of the form `0x9966LLCC`:
|
||||
|
||||
* Ready
|
||||
|
||||
In the figure, you’ll see that after exchanging any 32 bit value using SPI, some out of clock communication happens. This is the GBA and the Adapter signalling to each other that they are ready to communicate. This happens over the following stages:
|
||||
|
||||
1. The GBA goes low as soon as it can.
|
||||
2. The adapter goes high.
|
||||
3. The GBA goes high.
|
||||
4. The adapter goes low _when it’s ready_.
|
||||
5. The GBA goes low when it’s ready.
|
||||
6. The GBA starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
|
||||
- LL
|
||||
- The length of the data payload in number of 32 bit values. For example here it is `0x01`, so one value is transmitted after this.
|
||||
- CC
|
||||
- The command type, there are a bunch of these! In this case the command type is `0x17`.
|
||||
|
||||
- Data
|
||||
|
||||
All the data along with the command, must transmit the number given in the command
|
||||
|
||||
- Acknowledge
|
||||
|
||||
The adapter responds with a command, the length is the number of 32 bit values and the command type is always what you send + `0x80`. In this case the length is zero and the command is `0x17` + `0x80` = `0x97`.
|
||||
|
||||
- ⚠️ When you send invalid commands or a one you're not supposed to send in the current state (like sending a `0x1d` before a `0x1c`), the adapter responds `0x996601ee`. If you read the next word (as the response size is `01`), it gives you an error code (`2` when using an invalid command, `1` when using a valid command in an invalid state, or `0`).
|
||||
|
||||
- Response
|
||||
|
||||
The data that the adapter responds with. Equal to the length given in the acknowledgement.
|
||||
|
||||
- Ready
|
||||
|
||||
In the figure, you’ll see that after exchanging any 32 bit value using SPI, some out of clock communication happens. This is the GBA and the Adapter signalling to each other that they are ready to communicate. This happens over the following stages:
|
||||
|
||||
1. The GBA goes low as soon as it can.
|
||||
2. The adapter goes high.
|
||||
3. The GBA goes high.
|
||||
4. The adapter goes low _when it’s ready_.
|
||||
5. The GBA goes low when it’s ready.
|
||||
6. The GBA starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
|
||||
|
||||
⌛ 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).
|
||||
|
||||
|
|
@ -211,23 +205,22 @@ Whenever either side expects something to be sent from the other (as SPI is alwa
|
|||
|
||||
#### Hello - `0x10`
|
||||
|
||||
[](img/0x10.png)
|
||||
[](img/wireless/0x10.png)
|
||||
|
||||
* Send length: 0, Response length: 0
|
||||
|
||||
* First thing to be called after finishing the initialisation sequence.
|
||||
- Send length: 0, Response length: 0
|
||||
- First thing to be called after finishing the initialisation sequence.
|
||||
|
||||
#### Setup - `0x17`
|
||||
|
||||
[](img/0x17.png)
|
||||
[](img/wireless/0x17.png)
|
||||
|
||||
* Send length: 1, response length: 0
|
||||
|
||||
* Games set this. It seems to setup the adapter's configuration.
|
||||
- Send length: 1, response length: 0
|
||||
- Games set this. It seems to setup the adapter's configuration.
|
||||
|
||||
Both Pokemon games and the multiboot ROM that the adapter sends when no cartridge is inserted use `0x003C0420`.
|
||||
|
||||
🔝 For a game, the most important bits are bits `16-17` (let's call this `maxPlayers`), which specify the maximum number of allowed players:
|
||||
|
||||
- `00`: 5 players (1 host and 4 clients)
|
||||
- `01`: 4 players
|
||||
- `10`: 3 players
|
||||
|
|
@ -241,15 +234,14 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
|
||||
#### Broadcast - `0x16`
|
||||
|
||||
[](img/0x16.png)
|
||||
[](img/wireless/0x16.png)
|
||||
|
||||
* Send length: 6, response length: 0
|
||||
|
||||
* The data to be broadcast out to all adapters. Examples of use include the union room, broadcasting game name and username in download play, and the username in direct multiplayer in Pokémon.
|
||||
- Send length: 6, response length: 0
|
||||
- The data to be broadcast out to all adapters. Examples of use include the union room, broadcasting game name and username in download play, and the username in direct multiplayer in Pokémon.
|
||||
|
||||
💻 This is the first command used to start a server. The 6 parameters are the ASCII characters of the game and user name, plus some bytes indicating whether the server should appear in the Download Play list or not. Here's a byte by byte explanation:
|
||||
|
||||
[](img/broadcast.png)
|
||||
[](img/wireless/broadcast.png)
|
||||
|
||||
(if you read from right to left, it says `ICE CLIMBER` - `NINTENDO`)
|
||||
|
||||
|
|
@ -259,38 +251,35 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
|
||||
#### StartHost - `0x19`
|
||||
|
||||
* Send length: 0, response length: 0
|
||||
|
||||
* This uses the broadcast data given by the broadcast command and actually does the broadcasting.
|
||||
- Send length: 0, response length: 0
|
||||
- This uses the broadcast data given by the broadcast command and actually does the broadcasting.
|
||||
|
||||
#### EndHost - `0x1b`
|
||||
|
||||
* Send length: 0, response length: 2+
|
||||
|
||||
* This command stops host broadcast. This allows to "close" the session and stop allowing new clients, but also **keeping the existing connections alive**. Sends and Receives still work, but:
|
||||
- Clients cannot connect, even if they already know the host ID (`FinishConnection` will fail).
|
||||
- Calls to `AcceptConnections` on the host side will fail, unless `StartHost` is called again.
|
||||
|
||||
- Send length: 0, response length: 2+
|
||||
- This command stops host broadcast. This allows to "close" the session and stop allowing new clients, but also **keeping the existing connections alive**. Sends and Receives still work, but:
|
||||
- Clients cannot connect, even if they already know the host ID (`FinishConnection` will fail).
|
||||
- Calls to `AcceptConnections` on the host side will fail, unless `StartHost` is called again.
|
||||
|
||||
#### BroadcastRead - `0x1c`, `0x1d` and `0x1e`
|
||||
|
||||
[](img/0x1d.png)
|
||||
[](img/wireless/0x1d.png)
|
||||
|
||||
* Send length: 0, response length: 7 \* number of broadcasts (maximum: 4)
|
||||
|
||||
* All currently broadcasting devices are returned here along with a word of **metadata** (the metadata word first, then 6 words with broadcast data).
|
||||
* The metadata contains:
|
||||
* First 2 bytes: Server ID. IDs have 16 bits.
|
||||
* 3rd byte: Next available slot. This can be used to check whether a player can join a room or not.
|
||||
* `0b00`: If you join this room, your `clientNumber` will be 0.
|
||||
* `0b01`: If you join this room, your `clientNumber` will be 1.
|
||||
* `0b10`: If you join this room, your `clientNumber` will be 2.
|
||||
* `0b11`: If you join this room, your `clientNumber` will be 3.
|
||||
* `0xff`: The server is full. You cannot join this room.
|
||||
* Although `LinkWireless` uses this to know the number of connected players, that only works because -by design- rooms are closed when a player disconnects. The hardware allows disconnecting specific clients, so if the next available slot is e.g. 2, it can mean that there are 3 connected players (1 host + 2 clients) or that there are more players, but the third client (`clientNumber` = 2) has disconnected and the slot is now free.
|
||||
* The number of available slots depends on `maxPlayers` (see [Setup](#setup---0x17)) and/or [EndHost](#endhost---0x1b).
|
||||
* 4th byte: Zero.
|
||||
- Send length: 0, response length: 7 \* number of broadcasts (maximum: 4)
|
||||
- All currently broadcasting devices are returned here along with a word of **metadata** (the metadata word first, then 6 words with broadcast data).
|
||||
- The metadata contains:
|
||||
- First 2 bytes: Server ID. IDs have 16 bits.
|
||||
- 3rd byte: Next available slot. This can be used to check whether a player can join a room or not.
|
||||
- `0b00`: If you join this room, your `clientNumber` will be 0.
|
||||
- `0b01`: If you join this room, your `clientNumber` will be 1.
|
||||
- `0b10`: If you join this room, your `clientNumber` will be 2.
|
||||
- `0b11`: If you join this room, your `clientNumber` will be 3.
|
||||
- `0xff`: The server is full. You cannot join this room.
|
||||
- Although `LinkWireless` uses this to know the number of connected players, that only works because -by design- rooms are closed when a player disconnects. The hardware allows disconnecting specific clients, so if the next available slot is e.g. 2, it can mean that there are 3 connected players (1 host + 2 clients) or that there are more players, but the third client (`clientNumber` = 2) has disconnected and the slot is now free.
|
||||
- The number of available slots depends on `maxPlayers` (see [Setup](#setup---0x17)) and/or [EndHost](#endhost---0x1b).
|
||||
- 4th byte: Zero.
|
||||
|
||||
🆔 IDs are randomly generated. Each time you broadcast or connect, the adapter assigns you a new id.
|
||||
🆔 IDs are randomly generated. Each time you broadcast or connect, the adapter assigns you a new ID.
|
||||
|
||||
✅ Reading broadcasts is a three-step process: First, you send `0x1c` (you will get an ACK instantly), and start waiting until the adapter retrieves data (games usually wait 1 full second). Then, send a `0x1d` and it will return what's described above. Lastly, send a `0x1e` to finish the process (you can ignore what the adapter returns here). If you don't send that last `0x1e`, the next command will fail.
|
||||
|
||||
|
|
@ -300,105 +289,101 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
|
||||
#### AcceptConnections - `0x1a`
|
||||
|
||||
* Send length: 0, response length: 0+
|
||||
|
||||
* Accepts new connections and returns a list with the connected adapters. The length of the response is zero if there are no connected adapters.
|
||||
* It includes one value per connected client, in which the most significant byte is the `clientNumber` (see [IsFinishedConnect](#isfinishedconnect---0x20)) and the least significant byte is the ID.
|
||||
- Send length: 0, response length: 0+
|
||||
- Accepts new connections and returns a list with the connected adapters. The length of the response is zero if there are no connected adapters.
|
||||
- It includes one value per connected client, in which the most significant byte is the `clientNumber` (see [IsFinishedConnect](#isfinishedconnect---0x20)) and the least significant byte is the ID.
|
||||
|
||||
🔗 If this command reports 3 connected consoles, after turning off one of them, it will still report 3 consoles. Servers need to detect timeouts in another way.
|
||||
|
||||
#### Connect - `0x1f`
|
||||
|
||||
[](img/0x1f.png)
|
||||
[](img/wireless/0x1f.png)
|
||||
|
||||
* Send length: 1, response length: 0
|
||||
|
||||
* Send the ID of the adapter you want to connect to from [BroadcastRead](#broadcastread---0x1c-0x1d-and-0x1e).
|
||||
- Send length: 1, response length: 0
|
||||
- Send the ID of the adapter you want to connect to from [BroadcastRead](#broadcastread---0x1c-0x1d-and-0x1e).
|
||||
|
||||
#### IsFinishedConnect - `0x20`
|
||||
|
||||
[](img/0x20.png)
|
||||
[](img/wireless/0x20.png)
|
||||
|
||||
- Send length: 0, response length: 1
|
||||
- Responds with a 16 bit ID as lower 16 bits if finished, otherwise responds with `0x01000000`.
|
||||
|
||||
* Send length: 0, response length: 1
|
||||
|
||||
* Responds with a 16 bit ID as lower 16 bits if finished, otherwise responds with `0x01000000`.
|
||||
|
||||
👆 It also responds in its bits 16 and 17 a number that represents the `clientNumber` (0 to 3). Lets say our ID is `abcd`, it will respond `0x0000abcd` if we are the first client that connects to that server, `0x0001abcd` if we are the second one, `0x0002abcd` third, and `0x0003abcd` fourth. Rooms allow 5 simultaneous adapters at max.
|
||||
|
||||
💥 If the connection failed, the `clientNumber` will be a number higher than `3`.
|
||||
|
||||
#### FinishConnection - `0x21`
|
||||
|
||||
[](img/0x21.png)
|
||||
[](img/wireless/0x21.png)
|
||||
|
||||
* Send length: 0, response length: 1
|
||||
|
||||
* Called after [IsFinishedConnect](#isfinishedconnect---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
|
||||
- Send length: 0, response length: 1
|
||||
- Called after [IsFinishedConnect](#isfinishedconnect---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
|
||||
|
||||
#### SendData - `0x24`
|
||||
|
||||
* Send length: N, response length: 0
|
||||
|
||||
* Send N 32 bit values to connected adapter.
|
||||
|
||||
- Send length: N, response length: 0
|
||||
- Send N 32 bit values to connected adapter.
|
||||
|
||||
⚠️ The first value **is a header**, and has to be correct. Otherwise, the adapter will ignore the command and won't send any data. The header is as follows:
|
||||
|
||||
- For hosts: the number of `bytes` that come next. For example, if we want to send `0xaabbccdd` and `0x12345678` in the same command, we need to send:
|
||||
* `0x00000008`, `0xaabbccdd`, `0x12345678`.
|
||||
- `0x00000008`, `0xaabbccdd`, `0x12345678`.
|
||||
- For clients: `(bytes << (3 + (1+clientNumber) * 5))`. The `clientNumber` is what I described in [IsFinishedConnect](#isfinishedconnect---0x20). For example, if we want to send a single 4-byte value (`0xaabbccdd`):
|
||||
* The first client should send: `0x400`, `0xaabbccdd`
|
||||
* The second client should send: `0x8000`, `0xaabbccdd`
|
||||
* The third client should send: `0x100000`, `0xaabbccdd`
|
||||
* The fourth client should send: `0x2000000`, `0xaabbccdd`
|
||||
- The first client should send: `0x400`, `0xaabbccdd`
|
||||
- The second client should send: `0x8000`, `0xaabbccdd`
|
||||
- The third client should send: `0x100000`, `0xaabbccdd`
|
||||
- The fourth client should send: `0x2000000`, `0xaabbccdd`
|
||||
|
||||
🔝 Each `SendData` can send up to:
|
||||
|
||||
- **Host:** 87 bytes (or 21.75 values)
|
||||
- **Clients:** 16 bytes (or 4 values)
|
||||
- *(the header doesn't count)*
|
||||
- _(the header doesn't count)_
|
||||
|
||||
🗂️ Any non-multiple of 4 byte count will send LSB bytes first. For example, a host sending `0x00000003`, `0xaabbccdd` will result in bytes `0xbb`, `0xcc` and `0xdd` being received by clients (the clients will receive `0x00bbccdd`).
|
||||
|
||||
🤝 Note that when having more than 2 connected adapters, data is not transferred between different clients. If a client wants to tell something to another client, it has to talk first with the host with `SendData`, and then the host needs to relay that information to the other client.
|
||||
|
||||
👑 Internally, data is only sent when **the host** calls `SendData`:
|
||||
|
||||
- The send/receive buffer size is 1 packet, so calling `SendData` multiple times on either side (before the other side calls `ReceiveData`) will result in data loss.
|
||||
- Clients only **schedule** the data transfer, but they don't do it until the host sends something. This is problematic because the command overrides previously scheduled transfers, so calling `SendData` multiple times on the client side before the host calls `SendData` would also result in data loss. I believe this is why most games use `SendDataWait` on the client side.
|
||||
- Here's an example of this behavior:
|
||||
- **Client**: `SendData` `{sndHeader}`, `10`
|
||||
- **Host**: `SendData` `{sndHeader}`, `1` **(\*)**
|
||||
- *(here, the adapter internally receives the 10 from the client)*
|
||||
- **Host**: `SendData` `{sndHeader}`, `2`
|
||||
- *(here, the previous packet with 1 is lost since nobody received it yet)*
|
||||
- **Client**: `ReceiveData`
|
||||
- Receives `{rcvHeader}`, `2`
|
||||
- **Client**: `SendData` `{sndHeader}`, `20`
|
||||
- **Host**: `ReceiveData`
|
||||
- Receives `{rcvHeader}`, `10` *(pending from **(\*)**)*
|
||||
- **Host**: `ReceiveData`
|
||||
- Receives nothing
|
||||
- **Host**: `SendData` `{sndHeader}`, `3`
|
||||
- *(here, the adapter internally receives the 20 from the client)*
|
||||
- **Host**: `ReceiveData`
|
||||
- Receives `{rcvHeader}`, 20
|
||||
- **Client**: `SendData` `{sndHeader}`, `10`
|
||||
- **Host**: `SendData` `{sndHeader}`, `1` **(\*)**
|
||||
- _(here, the adapter internally receives the 10 from the client)_
|
||||
- **Host**: `SendData` `{sndHeader}`, `2`
|
||||
- _(here, the previous packet with 1 is lost since nobody received it yet)_
|
||||
- **Client**: `ReceiveData`
|
||||
- Receives `{rcvHeader}`, `2`
|
||||
- **Client**: `SendData` `{sndHeader}`, `20`
|
||||
- **Host**: `ReceiveData`
|
||||
- Receives `{rcvHeader}`, `10` _(pending from **(\*)**)_
|
||||
- **Host**: `ReceiveData`
|
||||
- Receives nothing
|
||||
- **Host**: `SendData` `{sndHeader}`, `3`
|
||||
- _(here, the adapter internally receives the 20 from the client)_
|
||||
- **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. Until we have a better name, we'll call this **ghost sends**.
|
||||
|
||||
#### SendDataWait - `0x25`
|
||||
|
||||
[](img/0x25.png)
|
||||
[](img/wireless/0x25.png)
|
||||
|
||||
* Send length: N, response length: 0
|
||||
|
||||
* The same as [SendData](#senddata---0x24) but with the additional effect of [Wait](#wait---0x27)
|
||||
* See [Waiting](#waiting) for more details on this.
|
||||
- Send length: N, response length: 0
|
||||
- The same as [SendData](#senddata---0x24) but with the additional effect of [Wait](#wait---0x27)
|
||||
- See [Waiting](#waiting) for more details on this.
|
||||
|
||||
#### ReceiveData - `0x26`
|
||||
|
||||
[](img/0x26.png)
|
||||
[](img/wireless/0x26.png)
|
||||
|
||||
* Send length: 0, response length: N
|
||||
|
||||
* Responds with all the data from all adapters. No IDs are included, this is just what was sent concatenated together.
|
||||
* Once data has been pulled out, it clears the data buffer, so calling this again can only get new data.
|
||||
- Send length: 0, response length: N
|
||||
- Responds with all the data from all adapters. No IDs are included, this is just what was sent concatenated together.
|
||||
- Once data has been pulled out, it clears the data buffer, so calling this again can only get new data.
|
||||
|
||||
🧩 The data is only concatenated on the host side, and its order is based on the `clientNumber`. It doesn't matter who called `SendData` first.
|
||||
|
||||
|
|
@ -412,91 +397,91 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
- The rest of the bits are `0`.
|
||||
|
||||
🧱 Concatenation is done at **byte** level. So, for example, if client 3 sends 3 bytes (`0xAABBCC`) and client 1 sends 2 bytes (`0xDDEE`), the host would receive 3 words:
|
||||
- (header) `0b0000_00011_00000_00000_00010_0_0000000`, `0xEEAABBCC`, `0x000000DD`
|
||||
|
||||
- (header) `0b0000_00011_00000_00000_00010_0_0000000`, `0xEEAABBCC`, `0x000000DD`
|
||||
|
||||
#### Wait - `0x27`
|
||||
|
||||
[](img/0x27.png)
|
||||
[](img/wireless/0x27.png)
|
||||
|
||||
- Send length: 0, response length: 0
|
||||
- See [Waiting](#waiting) for more details on this.
|
||||
|
||||
* Send length: 0, response length: 0
|
||||
|
||||
* See [Waiting](#waiting) for more details on this.
|
||||
|
||||
#### DisconnectClient - `0x30`
|
||||
|
||||
[](img/0x30.png)
|
||||
[](img/wireless/0x30.png)
|
||||
|
||||
* Send length 1, reponse length: 0
|
||||
|
||||
* This command disconnects clients. The argument is a bitmask of the client ID to disconnect. Sending `0x1` means "disconnect client number 0", sending `0x2` means "disconnect client number 1", and sending `0xF` would disconnect all the clients. After disconnecting a client, its ID won't appear on `AcceptConnection` calls and its `clientNumber` will be liberated, so other peers can connect.
|
||||
- Send length 1, reponse length: 0
|
||||
- This command disconnects clients. The argument is a bitmask of the client ID to disconnect. Sending `0x1` means "disconnect client number 0", sending `0x2` means "disconnect client number 1", and sending `0xF` would disconnect all the clients. After disconnecting a client, its ID won't appear on `AcceptConnection` calls and its `clientNumber` will be liberated, so other peers can connect.
|
||||
|
||||
⚡ The clients also are able to disconnect themselves using this command, but they can only send its corresponding bit or `0xF`, other bits are ignored (they cannot disconnect other clients). Also, the host won't know if a client disconnects itself, so this feature is not very useful:
|
||||
* The host still needs to monitor clients to ensure they are still alive (ie. through some PING like mechanism) and disconnect them if they are not, to allow new clients to connect. [4](#pokered)
|
||||
|
||||
- The host still needs to monitor clients to ensure they are still alive (ie. through some PING like mechanism) and disconnect them if they are not, to allow new clients to connect. [4](#pokered)
|
||||
|
||||
#### Bye - `0x3d`
|
||||
|
||||
* Send length: 0, Response length: 0
|
||||
|
||||
* This sets the adapter in a low power consumption mode. Games use it when the player exits the multiplayer mode. To use the adapter again after this command, a new reset/initialization is needed.
|
||||
- Send length: 0, Response length: 0
|
||||
- This sets the adapter in a low power consumption mode. Games use it when the player exits the multiplayer mode. To use the adapter again after this command, a new reset/initialization is needed.
|
||||
|
||||
### Other commands
|
||||
|
||||
#### SignalLevel - `0x11`
|
||||
|
||||
[](img/0x11.png)
|
||||
[](img/wireless/0x11.png)
|
||||
|
||||
* Send length: 0, response length: 1
|
||||
|
||||
* This returns the signal level of the other adapters from `0` to `0xFF` (`0` means disconnected).
|
||||
* The levels are returned in a single value, the first byte being the signal level of client 0, and the last byte being the signal level of client 3.
|
||||
* When called from a client, it only returns the signal level of that client in its corresponding byte. The rest of the bytes will be `0`.
|
||||
- Send length: 0, response length: 1
|
||||
- This returns the signal level of the other adapters from `0` to `0xFF` (`0` means disconnected).
|
||||
- The levels are returned in a single value, the first byte being the signal level of client 0, and the last byte being the signal level of client 3.
|
||||
- When called from a client, it only returns the signal level of that client in its corresponding byte. The rest of the bytes will be `0`.
|
||||
|
||||
#### VersionStatus - `0x12`
|
||||
|
||||
* Send length: 0, Response length: 1
|
||||
- Send length: 0, Response length: 1
|
||||
|
||||
* In my adapter, it always returns `8585495` (decimal). It contains the hardware and firmware version of the adapter.
|
||||
- In my adapter, it always returns `8585495` (decimal). It contains the hardware and firmware version of the adapter.
|
||||
|
||||
#### SystemStatus - `0x13`
|
||||
|
||||
* Send length: 0, Response length: 1
|
||||
- Send length: 0, Response length: 1
|
||||
|
||||
* Returns some information about the current connection and device state. The returned word contains:
|
||||
- Bits `0-15`: The device ID (or zero if the device is not connected nor hosting).
|
||||
- Bits `16-23`: A 4-bit array with slots. If the console is a client, it'll have a 1 in the position assigned to that slot (e.g. the one with `clientNumber` 3 will have `0100`). The host will always have `0000` here.
|
||||
- Bits `24-31`: A number indicating the state of the adapter
|
||||
- `0` = idle
|
||||
- `1`/`2` = serving (host)
|
||||
- `3` = searching
|
||||
- `4` = connecting
|
||||
- `5` = connected (client)
|
||||
- Returns some information about the current connection and device state. The returned word contains:
|
||||
|
||||
* Bits `0-15`: The device ID (or zero if the device is not connected nor hosting).
|
||||
* Bits `16-23`: A 4-bit array with slots. If the console is a client, it'll have a 1 in the position assigned to that slot (e.g. the one with `clientNumber` 3 will have `0100`). The host will always have `0000` here.
|
||||
* Bits `24-31`: A number indicating the state of the adapter
|
||||
- `0` = idle
|
||||
- `1`/`2` = serving (host)
|
||||
- `3` = searching
|
||||
- `4` = connecting
|
||||
- `5` = connected (client)
|
||||
|
||||
#### SlotStatus - `0x14`
|
||||
|
||||
* Send length: 0, Response length: 1+
|
||||
- Send length: 0, Response length: 1+
|
||||
|
||||
* It's returns a list of the connected adapters, similar to what `AcceptConnections` responds, but also:
|
||||
- It's returns a list of the connected adapters, similar to what `AcceptConnections` responds, but also:
|
||||
|
||||
- `SlotStatus` has an extra word at the start of the response, indicating the `clientNumber` that the next connection will have (or `0xFF` if the room is not accepting new clients).
|
||||
- `SlotStatus` can be called after `EndHost`, while `AcceptConnections` fails.
|
||||
- `SlotStatus` has an extra word at the start of the response, indicating the `clientNumber` that the next connection will have (or `0xFF` if the room is not accepting new clients).
|
||||
- `SlotStatus` can be called after `EndHost`, while `AcceptConnections` fails.
|
||||
|
||||
#### ConfigStatus - `0x15`
|
||||
|
||||
* Send length: 0, Response length: 7 (as client), or 8 (as host)
|
||||
* Returns the adapter configuration.
|
||||
- Send length: 0, Response length: 7 (as client), or 8 (as host)
|
||||
- Returns the adapter configuration.
|
||||
|
||||
🤔 In my tests...
|
||||
|
||||
- As client, it returned: `0, 0, 0, 0, 0, 0, 257`.
|
||||
- As host, it returned: `1, 2, 3, 4, 5, 6, 3933216, 257`.
|
||||
* `1, 2, 3, 4, 5, 6` would be the broadcast data.
|
||||
* `3933216` is the value used in the [Setup](#setup---0x17) command (`0x003C0420`).
|
||||
* No idea what `257` this means.
|
||||
- `1, 2, 3, 4, 5, 6` would be the broadcast data.
|
||||
- `3933216` is the value used in the [Setup](#setup---0x17) command (`0x003C0420`).
|
||||
- No idea what `257` this means.
|
||||
|
||||
#### RetransmitAndWait - `0x37`
|
||||
|
||||
* Send length: 0, Response length: 0
|
||||
* Retransmits the last data from a host to all clients, with the additional effect of [Wait](#wait---0x27)
|
||||
* See [Waiting](#waiting) for more details on this.
|
||||
- Send length: 0, Response length: 0
|
||||
- Retransmits the last data from a host to all clients, with the additional effect of [Wait](#wait---0x27)
|
||||
- See [Waiting](#waiting) for more details on this.
|
||||
|
||||
### Unknown commands
|
||||
|
||||
|
|
@ -510,16 +495,15 @@ If we analyze whether a command ID throws an 'invalid command' error (`0x996601e
|
|||
- `0x38`
|
||||
- `0x39`
|
||||
|
||||
Waiting
|
||||
-------
|
||||
## Waiting
|
||||
|
||||
[](img/wake-up.png)
|
||||
[](img/wireless/wake-up.png)
|
||||
|
||||
* After either [SendDataWait](#senddatawait---0x25) or [Wait](#wait---0x27), clock control switches to the wireless adapter.
|
||||
* Once the adapter has something to tell the GBA about, the _adapter_ sends a command to the GBA (usually `0x99660028`).
|
||||
* These transfers are dealt with in much the same way as before but with the roles of the GBA and the adapter reversed, see the figure!
|
||||
* 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).
|
||||
- After either [SendDataWait](#senddatawait---0x25) or [Wait](#wait---0x27), clock control switches to the wireless adapter.
|
||||
- Once the adapter has something to tell the GBA about, the _adapter_ sends a command to the GBA (usually `0x99660028`).
|
||||
- These transfers are dealt with in much the same way as before but with the roles of the GBA and the adapter reversed, see the figure!
|
||||
- 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](#setup---0x17) command before**. The value that most games use (`0x003C0420`) contains this timeout value, but the default is zero (no timeout).
|
||||
|
||||
|
|
@ -528,14 +512,16 @@ Waiting
|
|||
💨 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.
|
||||
|
||||
- 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
|
||||
|
||||
- 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**
|
||||
|
||||
|
|
@ -548,20 +534,19 @@ While the clock is inverted, the acknowledge procedure is 'standard' but with th
|
|||
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
|
||||
------------------
|
||||
## 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- 3. The host sends the rom bytes in 84-byte chunks.
|
||||
|
||||
- 4) The host sends a 'rom end' command and the games boot.
|
||||
- 4. The host sends a 'rom end' command and the games boot.
|
||||
|
||||
### Valid header
|
||||
|
||||
|
|
@ -573,6 +558,8 @@ The bootloader will only accept ROMs with valid headers: they must contain this
|
|||
|
||||
When the bootloader accepts the ROM, it will run the jump instruction located at the first byte. No extra headers are required apart from these 16 bytes.
|
||||
|
||||
`LinkWirelessMultiboot` patches the header on the fly, so users can compile their ROMs for cabled Multiboot and also use them wirelessly.
|
||||
|
||||
### Official protocol
|
||||
|
||||
> You can learn more details by reading [LinkWirelessOpenSDK.hpp](../lib/LinkWirelessOpenSDK.hpp)'s code.
|
||||
|
|
@ -635,37 +622,37 @@ enum CommState : unsigned int {
|
|||
- 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`
|
||||
- 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-`
|
||||
- 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`
|
||||
- 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`
|
||||
- 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`
|
||||
- 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`
|
||||
- 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
|
||||
- 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
|
||||
- Header: `0x0080` (`size=0, n=1, ph=0, ack=0, commState=0`) (`0 = OFF`)
|
||||
- No payload
|
||||
- Server: ACKs the packet
|
||||
|
||||
## (2) ROM start command
|
||||
|
|
@ -689,22 +676,18 @@ 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
|
||||
----------
|
||||
## SPI config
|
||||
|
||||
Here's how SPI works on the GBA:
|
||||
|
||||
[](img/logic2.png)
|
||||
[](img/wireless/logic2.png)
|
||||
|
||||
I know more!
|
||||
============
|
||||
# I know more!
|
||||
|
||||
If you know any extra details about the wireless adapter, get in touch!. For specific details I’ve left footnotes around if you happen to know that piece of information.
|
||||
|
||||
1. Multiboot is what we call a rom that can be booted over link cable. This can be used for something akin to download play software for the DS. [↩︎](#fnref:multiboot)
|
||||
|
||||
2. [Games compatible with the wireless adapter](https://en.wikipedia.org/wiki/Game_Boy_Advance_Wireless_Adapter#Compatible_games) [↩︎](#fnref:list_of_games)
|
||||
|
||||
3. [Send me an email if you know more about this](https://blog.kuiper.dev/contact)
|
||||
|
||||
4. Some interesting data about the RFU adapter can be found in Pokemon Games, see the [FireRed Decompilation](https://github.com/pret/pokefirered/blob/49ea462d7f421e75a76b25d7e85c92494c0a9798/include/librfu.h#L44) for more information.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -134,22 +134,22 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../_lib/libgbfs
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
||||
# --- switches ---
|
||||
|
||||
bMB := 1 # Multiboot build
|
||||
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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
@ -247,9 +248,6 @@ $(BUILD):
|
|||
@[ -d $@ ] || mkdir -p $@
|
||||
@make --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||
arm-none-eabi-nm -Sn $(OUTPUT).elf > $(BUILD)/$(TARGET).map
|
||||
mv $(OUTPUT).gba tmp.gba
|
||||
./pad16.sh tmp.gba
|
||||
mv tmp.gba $(OUTPUT).gba
|
||||
|
||||
all : $(BUILD)
|
||||
|
||||
|
|
@ -278,10 +276,13 @@ endif # End BUILD switch
|
|||
|
||||
.PHONY: clean rebuild start
|
||||
|
||||
rebuild: clean $(BUILD)
|
||||
rebuild: clean $(BUILD) package
|
||||
|
||||
start:
|
||||
start "$(TARGET).gba"
|
||||
package: $(BUILD)
|
||||
../gbfs.sh "$(TARGET).gba" content.gbfs "$(TARGET).out.gba"
|
||||
|
||||
start: package
|
||||
start "$(TARGET).out.gba"
|
||||
|
||||
restart: rebuild start
|
||||
|
||||
|
|
|
|||
BIN
examples/LinkCableMultiboot_demo/content.gbfs
Normal file
|
|
@ -1,128 +1,138 @@
|
|||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
#include "../../../lib/LinkCable.hpp"
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkCableMultiboot.hpp"
|
||||
|
||||
void log(std::string text);
|
||||
#include <string.h>
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
#include "../../_lib/libgbfs/gbfs.h"
|
||||
}
|
||||
|
||||
static const GBFS_FILE* fs = find_first_gbfs_file(0);
|
||||
static u32 selectedFile = 0;
|
||||
static bool spi = false;
|
||||
|
||||
void log(std::string text);
|
||||
void waitFor(u16 key);
|
||||
bool didPress(u16 key, bool& pressed);
|
||||
|
||||
LinkCable* linkCable = new LinkCable();
|
||||
// (1) Create a LinkCableMultiboot instance
|
||||
LinkCableMultiboot* linkCableMultiboot = new LinkCableMultiboot();
|
||||
|
||||
void selectLeft() {
|
||||
if (selectedFile == 0)
|
||||
return;
|
||||
selectedFile--;
|
||||
}
|
||||
|
||||
void selectRight() {
|
||||
if ((int)selectedFile >= fs->dir_nmemb - 1)
|
||||
return;
|
||||
selectedFile++;
|
||||
}
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_CABLE_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_CABLE_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
irq_init(NULL);
|
||||
irq_add(II_VBLANK, NULL);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
// 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 = 39264;
|
||||
// Note that this project's Makefile pads the ROM to a 0x10 boundary
|
||||
// (as required for Multiboot).
|
||||
// Ensure there are GBFS files
|
||||
if (fs == NULL) {
|
||||
log("! GBFS file not found");
|
||||
while (true)
|
||||
;
|
||||
} else if (gbfs_get_nth_obj(fs, 0, NULL, NULL) == NULL) {
|
||||
log("! No files found (GBFS)");
|
||||
while (true)
|
||||
;
|
||||
}
|
||||
|
||||
// Each player's input
|
||||
u16 data[LINK_CABLE_MAX_PLAYERS];
|
||||
for (u32 i = 0; i < LINK_CABLE_MAX_PLAYERS; i++)
|
||||
data[i] = 0;
|
||||
|
||||
bool isSenderMode = true;
|
||||
LinkCableMultiboot::Result result = LinkCableMultiboot::Result::CANCELED;
|
||||
bool left = false, right = false, a = false, b = false, l = false;
|
||||
|
||||
while (true) {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
// Sender options
|
||||
if (isSenderMode) {
|
||||
if (result != LinkCableMultiboot::Result::SUCCESS)
|
||||
log("LinkCableMultiboot_demo\n (v6.3.0)\n\nPress START to send the "
|
||||
"ROM...\nPress B to set client mode...");
|
||||
|
||||
if (keys & KEY_START) {
|
||||
log("Sending... (SELECT to cancel)");
|
||||
linkCable->deactivate();
|
||||
|
||||
// (3) Send the ROM
|
||||
result =
|
||||
linkCableMultiboot->sendRom((const u8*)MEM_EWRAM, romSize, []() {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
return keys & KEY_SELECT;
|
||||
});
|
||||
|
||||
// Print results and wait
|
||||
log("Result: " + std::to_string(result) + "\n" +
|
||||
"Press A to continue...");
|
||||
do {
|
||||
keys = ~REG_KEYS & KEY_ANY;
|
||||
} while (!(keys & KEY_A));
|
||||
|
||||
// Switch to client mode if it worked
|
||||
if (result == LinkCableMultiboot::Result::SUCCESS) {
|
||||
linkCable->activate();
|
||||
VBlankIntrWait();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to client mode manually (for slaves)
|
||||
if (keys & KEY_B) {
|
||||
isSenderMode = false;
|
||||
linkCable->activate();
|
||||
VBlankIntrWait();
|
||||
continue;
|
||||
}
|
||||
|
||||
// In sender mode, don't continue until the ROM is sent successfully
|
||||
if (result != LinkCableMultiboot::Result::SUCCESS) {
|
||||
VBlankIntrWait();
|
||||
continue;
|
||||
// Get selected ROM name
|
||||
char name[32];
|
||||
u32 romSize;
|
||||
const u8* romToSend =
|
||||
(const u8*)gbfs_get_nth_obj(fs, selectedFile, name, &romSize);
|
||||
for (u32 i = 0; i < 32; i++) {
|
||||
if (name[i] == '.') {
|
||||
name[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ---
|
||||
// Client mode
|
||||
// ---
|
||||
// Toggle mode
|
||||
if (didPress(KEY_L, l))
|
||||
spi = !spi;
|
||||
|
||||
linkCable->sync();
|
||||
linkCable->send(keys + 1);
|
||||
// Select ROM
|
||||
if (didPress(KEY_LEFT, left))
|
||||
selectLeft();
|
||||
if (didPress(KEY_RIGHT, right))
|
||||
selectRight();
|
||||
|
||||
std::string output = "";
|
||||
if (linkCable->isConnected()) {
|
||||
u8 playerCount = linkCable->playerCount();
|
||||
u8 currentPlayerId = linkCable->currentPlayerId();
|
||||
// Menu
|
||||
log("LinkCableMultiboot_demo\n (v7.0.0)\n\n"
|
||||
"Press A to send the ROM...\n"
|
||||
"Press B to launch the ROM...\nLEFT/RIGHT: select ROM\nL: toggle "
|
||||
"mode\n\nSelected ROM:\n " +
|
||||
std::string(name) + "\n\nMode:\n " +
|
||||
std::string(spi ? "SPI (GBC cable)" : "MULTI_PLAY (GBA cable)"));
|
||||
|
||||
output += "Players: " + std::to_string(playerCount) + "\n";
|
||||
// Send ROM
|
||||
if (didPress(KEY_A, a)) {
|
||||
log("Sending... (SELECT to cancel)");
|
||||
|
||||
output += "(";
|
||||
for (u32 i = 0; i < playerCount; i++) {
|
||||
while (linkCable->canRead(i))
|
||||
data[i] = linkCable->read(i) - 1;
|
||||
// (2) Send the ROM
|
||||
auto result = linkCableMultiboot->sendRom(
|
||||
romToSend, romSize,
|
||||
[]() {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
return keys & KEY_SELECT;
|
||||
},
|
||||
spi ? LinkCableMultiboot::TransferMode::SPI
|
||||
: LinkCableMultiboot::TransferMode::MULTI_PLAY);
|
||||
|
||||
output += std::to_string(data[i]) + (i + 1 == playerCount ? "" : ", ");
|
||||
}
|
||||
output += ")\n";
|
||||
output += "_keys: " + std::to_string(keys) + "\n";
|
||||
output += "_pID: " + std::to_string(currentPlayerId);
|
||||
} else {
|
||||
output += std::string("Waiting... ");
|
||||
// Print results and wait
|
||||
log("Result: " + std::to_string(result) + "\n" +
|
||||
"Press DOWN to continue...");
|
||||
waitFor(KEY_DOWN);
|
||||
}
|
||||
|
||||
// Launch ROM
|
||||
if (didPress(KEY_B, b)) {
|
||||
log("Launching...");
|
||||
VBlankIntrWait();
|
||||
|
||||
REG_IME = 0;
|
||||
|
||||
u32 fileLength;
|
||||
const u8* romToSend =
|
||||
(const u8*)gbfs_get_nth_obj(fs, selectedFile, NULL, &fileLength);
|
||||
|
||||
void* EWRAM = (void*)0x02000000;
|
||||
memcpy(EWRAM, romToSend, fileLength);
|
||||
|
||||
asm volatile(
|
||||
"mov r0, %0\n"
|
||||
"bx r0\n"
|
||||
:
|
||||
: "r"(EWRAM)
|
||||
: "r0");
|
||||
|
||||
while (true)
|
||||
;
|
||||
}
|
||||
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -132,4 +142,23 @@ void log(std::string text) {
|
|||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void waitFor(u16 key) {
|
||||
u16 keys;
|
||||
do {
|
||||
keys = ~REG_KEYS & KEY_ANY;
|
||||
} while (!(keys & key));
|
||||
}
|
||||
|
||||
bool didPress(u16 key, bool& pressed) {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
bool isPressedNow = false;
|
||||
if ((keys & key) && !pressed) {
|
||||
pressed = true;
|
||||
isPressedNow = true;
|
||||
}
|
||||
if (pressed && !(keys & key))
|
||||
pressed = false;
|
||||
return isPressedNow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// BASIC:
|
||||
// This example sends the pressed buttons to other players.
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkCable.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
void log(std::string text);
|
||||
|
||||
// (1) Create a LinkCable instance
|
||||
|
|
@ -45,7 +45,7 @@ int main() {
|
|||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
linkCable->send(keys + 1); // (avoid using 0)
|
||||
|
||||
std::string output = "LinkCable_basic (v6.3.0)\n\n";
|
||||
std::string output = "LinkCable_basic (v7.0.0)\n\n";
|
||||
if (linkCable->isConnected()) {
|
||||
u8 playerCount = linkCable->playerCount();
|
||||
u8 currentPlayerId = linkCable->currentPlayerId();
|
||||
|
|
@ -77,4 +77,4 @@ void log(std::string text) {
|
|||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ BUILD := build
|
|||
SRCDIRS := src ../_lib ../../lib \
|
||||
src/scenes \
|
||||
src/utils
|
||||
|
||||
DATADIRS :=
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
|
||||
|
|
@ -156,10 +155,11 @@ CFLAGS += -Wall
|
|||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// FULL:
|
||||
// This example has a menu and lets the user send data in different ways.
|
||||
|
||||
#include "main.h"
|
||||
#include <libgba-sprite-engine/gba_engine.h>
|
||||
#include "../../_lib/interrupt.h"
|
||||
#include "scenes/TestScene.h"
|
||||
#include "utils/SceneUtils.h"
|
||||
|
||||
// FULL:
|
||||
// This example has a menu and lets the user send data in different ways.
|
||||
|
||||
void setUpInterrupts();
|
||||
void printTutorial();
|
||||
static std::shared_ptr<GBAEngine> engine{new GBAEngine()};
|
||||
|
|
@ -15,8 +15,7 @@ static std::unique_ptr<TestScene> testScene{new TestScene(engine)};
|
|||
#ifndef USE_LINK_UNIVERSAL
|
||||
LinkCable* linkCable = new LinkCable();
|
||||
LinkCable* linkConnection = linkCable;
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
#else
|
||||
LinkUniversal* linkUniversal = new LinkUniversal();
|
||||
LinkUniversal* linkConnection = linkUniversal;
|
||||
#endif
|
||||
|
|
@ -41,13 +40,17 @@ int main() {
|
|||
DEBULOG("! started");
|
||||
}
|
||||
|
||||
// log player id/count and important flags
|
||||
static constexpr int BIT_READY = 3;
|
||||
static constexpr int BIT_ERROR = 6;
|
||||
static constexpr int BIT_START = 7;
|
||||
|
||||
// log player ID/count and important flags
|
||||
TextStream::instance().setText(
|
||||
"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)),
|
||||
asStr(isBitHigh(REG_SIOCNT, BIT_READY)) + "-S" +
|
||||
asStr(isBitHigh(REG_SIOCNT, BIT_ERROR)) + "-E" +
|
||||
asStr(isBitHigh(REG_SIOCNT, BIT_START)),
|
||||
0, 14);
|
||||
|
||||
engine->update();
|
||||
|
|
@ -74,8 +77,7 @@ inline void setUpInterrupts() {
|
|||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
#else
|
||||
// LinkUniversal
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_UNIVERSAL_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
|
|
@ -93,10 +95,9 @@ inline void setUpInterrupts() {
|
|||
|
||||
void printTutorial() {
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
DEBULOG("LinkCable_full (v6.3.0)");
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
DEBULOG("LinkUniversal_full (v6.3.0)");
|
||||
DEBULOG("LinkCable_full (v7.0.0)");
|
||||
#else
|
||||
DEBULOG("LinkUniversal_full (v7.0.0)");
|
||||
#endif
|
||||
|
||||
DEBULOG("");
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include <tonc.h>
|
||||
#include "../../../lib/LinkCable.hpp"
|
||||
#include "../../../lib/LinkUniversal.hpp"
|
||||
|
||||
// #define USE_LINK_UNIVERSAL
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
extern LinkCable* linkConnection;
|
||||
#include "../../../lib/LinkCable.hpp"
|
||||
#else
|
||||
#include "../../../lib/LinkUniversal.hpp"
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
extern LinkCable* linkConnection;
|
||||
#else
|
||||
extern LinkUniversal* linkConnection;
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -36,4 +36,4 @@ class InputHandler {
|
|||
bool isWaiting = false;
|
||||
};
|
||||
|
||||
#endif // INPUT_HANDLER_H
|
||||
#endif // INPUT_HANDLER_H
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
#include "main.h"
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// STRESS:
|
||||
// This example can perform multiple stress tests.
|
||||
// A) Packet loss test:
|
||||
|
|
@ -18,6 +14,10 @@
|
|||
// R) Measure ping-pong latency:
|
||||
// - Like (L), but adding a validation response and adding that time.
|
||||
|
||||
#include "main.h"
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
#define FINAL_VALUE 65534
|
||||
|
||||
void test(bool withSync);
|
||||
|
|
@ -34,25 +34,22 @@ u32 toMs(u32 cycles);
|
|||
#ifndef USE_LINK_UNIVERSAL
|
||||
LinkCable* linkCable = new LinkCable();
|
||||
LinkCable* linkConnection = linkCable;
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
#else
|
||||
LinkUniversal* linkUniversal =
|
||||
new LinkUniversal(LinkUniversal::Protocol::AUTODETECT,
|
||||
"LinkUniversal",
|
||||
(LinkUniversal::CableOptions){
|
||||
.baudRate = LinkCable::BAUD_RATE_1,
|
||||
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
|
||||
.remoteTimeout = LINK_CABLE_DEFAULT_REMOTE_TIMEOUT,
|
||||
.interval = LINK_CABLE_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
|
||||
(LinkUniversal::WirelessOptions){
|
||||
.retransmission = true,
|
||||
.maxPlayers = 2,
|
||||
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
|
||||
.remoteTimeout = LINK_WIRELESS_DEFAULT_REMOTE_TIMEOUT,
|
||||
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
|
||||
.asyncACKTimerId = 0});
|
||||
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID},
|
||||
__qran_seed);
|
||||
LinkUniversal* linkConnection = linkUniversal;
|
||||
#endif
|
||||
|
||||
|
|
@ -70,8 +67,7 @@ void init() {
|
|||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
#else
|
||||
// LinkUniversal
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_UNIVERSAL_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
|
|
@ -79,8 +75,6 @@ void init() {
|
|||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_UNIVERSAL_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
interrupt_set_handler(INTR_TIMER0, LINK_UNIVERSAL_ISR_ACK_TIMER);
|
||||
interrupt_enable(INTR_TIMER0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -89,10 +83,9 @@ int main() {
|
|||
|
||||
while (true) {
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
std::string output = "LinkCable_stress (v6.3.0)\n\n";
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
std::string output = "LinkUniversal_stress (v6.3.0)\n\n";
|
||||
std::string output = "LinkCable_stress (v7.0.0)\n\n";
|
||||
#else
|
||||
std::string output = "LinkUniversal_stress (v7.0.0)\n\n";
|
||||
#endif
|
||||
|
||||
linkConnection->deactivate();
|
||||
|
|
@ -117,8 +110,7 @@ int main() {
|
|||
interval = 10;
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
linkConnection->config.interval = interval;
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
#else
|
||||
linkConnection->linkCable->config.interval = interval;
|
||||
linkConnection->linkWireless->config.interval = interval;
|
||||
#endif
|
||||
|
|
@ -292,11 +284,13 @@ void measureLatency(bool withPong) {
|
|||
totalMs += elapsedMilliseconds;
|
||||
u32 average = Div(totalMs, samples);
|
||||
|
||||
std::string output = "Ping latency: \n " +
|
||||
std::to_string(elapsedCycles) + " cycles\n " +
|
||||
std::to_string(elapsedMilliseconds) + " ms\n " +
|
||||
std::to_string(average) + " ms avg" +
|
||||
"\nValue sent:\n " + std::to_string(sentPacket);
|
||||
VBlankIntrWait();
|
||||
log("Ping latency: \n " + std::to_string(elapsedCycles) + " cycles\n " +
|
||||
std::to_string(elapsedMilliseconds) + " ms\n " +
|
||||
std::to_string(average) + " ms avg" + "\nValue sent:\n " +
|
||||
std::to_string(sentPacket));
|
||||
log(output);
|
||||
} else {
|
||||
VBlankIntrWait();
|
||||
log("Waiting...");
|
||||
|
|
@ -366,4 +360,4 @@ u32 toMs(u32 cycles) {
|
|||
// CPU Frequency * time per frame = cycles per frame
|
||||
// 16780000 * (1/60) ~= 279666
|
||||
return (cycles * 1000) / (279666 * 60);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include <tonc.h>
|
||||
#include "../../../lib/LinkCable.hpp"
|
||||
#include "../../../lib/LinkUniversal.hpp"
|
||||
|
||||
// #define USE_LINK_UNIVERSAL
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
extern LinkCable* linkConnection;
|
||||
#include "../../../lib/LinkCable.hpp"
|
||||
#else
|
||||
#include "../../../lib/LinkUniversal.hpp"
|
||||
#endif
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
|
||||
#include <tonc.h>
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
extern LinkCable* linkConnection;
|
||||
#else
|
||||
extern LinkUniversal* linkConnection;
|
||||
#endif
|
||||
|
||||
|
|
|
|||
285
examples/LinkCube_demo/Makefile
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#
|
||||
# Template tonc makefile
|
||||
#
|
||||
# Yoinked mostly from DKP's template
|
||||
#
|
||||
|
||||
# === 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
|
||||
# =====================================================================
|
||||
|
||||
# --- Main path ---
|
||||
|
||||
export PATH := $(DEVKITARM)/bin:$(PATH)
|
||||
|
||||
|
||||
# === 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
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
||||
# --- 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
|
||||
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-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
|
||||
|
||||
|
||||
# === 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)
|
||||
|
||||
start:
|
||||
start "$(TARGET).gba"
|
||||
|
||||
restart: rebuild start
|
||||
|
||||
# EOF
|
||||
120
examples/LinkCube_demo/src/main.cpp
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkCube.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
void log(std::string text);
|
||||
void wait(u32 verticalLines);
|
||||
bool didPress(unsigned short key, bool& pressed);
|
||||
inline void VBLANK() {}
|
||||
|
||||
bool a = false, b = false, l = false;
|
||||
|
||||
// (1) Create a LinkCube instance
|
||||
LinkCube* linkCube = new LinkCube();
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
|
||||
// (2) Add the interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_CUBE_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
// (3) Initialize the library
|
||||
linkCube->activate();
|
||||
|
||||
u32 counter = 0;
|
||||
std::string received = "";
|
||||
bool reset = false;
|
||||
u32 vblanks = 0;
|
||||
|
||||
while (true) {
|
||||
// Title
|
||||
std::string output =
|
||||
"LinkCube_demo (v7.0.0)" + std::string(reset ? " !RESET!" : "") +
|
||||
"\n\nPress A to send\nPress B to clear\n (L = "
|
||||
"+1024)\n\nLast sent: " +
|
||||
std::to_string(counter) +
|
||||
"\n(pending = " + std::to_string(linkCube->pendingCount()) +
|
||||
")\n\nReceived:\n" + received;
|
||||
|
||||
// (4) Send 32-bit values
|
||||
if (didPress(KEY_A, a)) {
|
||||
counter++;
|
||||
linkCube->send(counter);
|
||||
}
|
||||
|
||||
// +1024
|
||||
if (didPress(KEY_L, l)) {
|
||||
counter += 1024;
|
||||
linkCube->send(counter);
|
||||
}
|
||||
|
||||
// (5) Read 32-bit values
|
||||
while (linkCube->canRead()) {
|
||||
received += std::to_string(linkCube->read()) + ", ";
|
||||
}
|
||||
|
||||
// Clear
|
||||
if (didPress(KEY_B, b))
|
||||
received = "";
|
||||
|
||||
// Reset warning
|
||||
if (linkCube->didReset()) {
|
||||
counter = 0;
|
||||
reset = true;
|
||||
vblanks = 0;
|
||||
}
|
||||
if (reset) {
|
||||
vblanks++;
|
||||
if (vblanks > 60)
|
||||
reset = false;
|
||||
}
|
||||
|
||||
// Print
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log(std::string text) {
|
||||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
|
||||
void wait(u32 verticalLines) {
|
||||
u32 count = 0;
|
||||
u32 vCount = REG_VCOUNT;
|
||||
|
||||
while (count < verticalLines) {
|
||||
if (REG_VCOUNT != vCount) {
|
||||
count++;
|
||||
vCount = REG_VCOUNT;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool didPress(u16 key, bool& pressed) {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
bool isPressedNow = false;
|
||||
if ((keys & key) && !pressed) {
|
||||
pressed = true;
|
||||
isPressedNow = true;
|
||||
}
|
||||
if (pressed && !(keys & key))
|
||||
pressed = false;
|
||||
return isPressedNow;
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
#include <tonc.h>
|
||||
#include <string>
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkGPIO.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
|
||||
void log(std::string text);
|
||||
std::string mode(std::string name, LinkGPIO::Pin pin);
|
||||
std::string value(std::string name, LinkGPIO::Pin pin, bool isHigh);
|
||||
|
|
@ -27,7 +27,7 @@ int main() {
|
|||
|
||||
while (true) {
|
||||
// (3) Use the pins
|
||||
std::string output = "LinkGPIO_demo (v6.3.0)\n\n";
|
||||
std::string output = "LinkGPIO_demo (v7.0.0)\n\n";
|
||||
|
||||
// Commands
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
|
@ -104,4 +104,4 @@ std::string value(std::string name, LinkGPIO::Pin pin, bool isHigh) {
|
|||
return linkGPIO->getMode(pin) == LinkGPIO::Direction::INPUT
|
||||
? "< " + title + std::to_string(linkGPIO->readPin(pin)) + "\n"
|
||||
: "> " + title + std::to_string(isHigh) + "\n";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
285
examples/LinkMobile_demo/Makefile
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#
|
||||
# Template tonc makefile
|
||||
#
|
||||
# Yoinked mostly from DKP's template
|
||||
#
|
||||
|
||||
# === 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
|
||||
# =====================================================================
|
||||
|
||||
# --- Main path ---
|
||||
|
||||
export PATH := $(DEVKITARM)/bin:$(PATH)
|
||||
|
||||
|
||||
# === 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
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
||||
# --- 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
|
||||
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-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
|
||||
|
||||
|
||||
# === 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)
|
||||
|
||||
start:
|
||||
start "$(TARGET).gba"
|
||||
|
||||
restart: rebuild start
|
||||
|
||||
# EOF
|
||||
635
examples/LinkMobile_demo/src/main.cpp
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
#define LINK_ENABLE_DEBUG_LOGS 0
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkMobile.hpp"
|
||||
|
||||
#include "main.h"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <functional>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// One transfer for every N frames
|
||||
constexpr static int TRANSFER_FREQUENCY = 30;
|
||||
|
||||
bool isConnected = false;
|
||||
bool hasError = false;
|
||||
u16 keys = 0;
|
||||
std::string output = "";
|
||||
|
||||
LinkMobile::DataTransfer dataTransfer;
|
||||
LinkMobile::DataTransfer lastCompletedTransfer;
|
||||
LinkMobile::DNSQuery dnsQuery;
|
||||
bool waitingDNS = false;
|
||||
std::string outgoingData = "";
|
||||
u32 counter = 0;
|
||||
u32 frameCounter = 0;
|
||||
|
||||
bool left = false, right = false, up = false, down = false;
|
||||
bool a = false, b = false, l = false, r = false;
|
||||
bool start = false, select = false;
|
||||
std::string selectedNumber = "";
|
||||
std::string selectedPassword = "";
|
||||
std::string selectedDomain = "";
|
||||
|
||||
LinkMobile* linkMobile = nullptr;
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
start:
|
||||
log("LinkMobile_demo (v7.0.0)\n\nPress A to start");
|
||||
waitForA();
|
||||
|
||||
// (1) Create a LinkMobile instance
|
||||
linkMobile = new LinkMobile();
|
||||
|
||||
// (2) Add the required interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_MOBILE_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_MOBILE_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_MOBILE_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
|
||||
// (3) Initialize the library
|
||||
linkMobile->activate();
|
||||
|
||||
while (true) {
|
||||
keys = ~REG_KEYS & KEY_ANY;
|
||||
hasError = linkMobile->getError().type != LinkMobile::Error::Type::NONE;
|
||||
output = "State = " + getStateString(linkMobile->getState()) + "\n";
|
||||
|
||||
printMenu();
|
||||
|
||||
if (linkMobile->isConnectedP2P()) {
|
||||
handleP2P();
|
||||
} else if (linkMobile->isConnectedPPP()) {
|
||||
handlePPP();
|
||||
} else if (isConnected) {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
// SELECT = stop
|
||||
if (didPress(KEY_SELECT, select)) {
|
||||
bool didShutdown = linkMobile->getState() == LinkMobile::State::SHUTDOWN;
|
||||
|
||||
if (hasError || didShutdown) {
|
||||
linkMobile->deactivate();
|
||||
interrupt_disable(INTR_VBLANK);
|
||||
interrupt_disable(INTR_SERIAL);
|
||||
interrupt_disable(INTR_TIMER3);
|
||||
delete linkMobile;
|
||||
linkMobile = nullptr;
|
||||
|
||||
if (!didShutdown) {
|
||||
log("Waiting...");
|
||||
wait(228 * 60 * 3);
|
||||
}
|
||||
|
||||
goto start;
|
||||
} else if (linkMobile->canShutdown()) {
|
||||
// (12) Turn off the adapter
|
||||
linkMobile->shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
switch (linkMobile->getState()) {
|
||||
case LinkMobile::State::SESSION_ACTIVE: {
|
||||
// L = Read Configuration
|
||||
if (didPress(KEY_L, l)) {
|
||||
readConfiguration();
|
||||
waitForA();
|
||||
}
|
||||
|
||||
// R = Call someone
|
||||
if (didPress(KEY_R, r)) {
|
||||
std::string number = getNumberInput();
|
||||
if (number != "") {
|
||||
// (4) Call someone
|
||||
linkMobile->call(number.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// START = Call the ISP
|
||||
if (didPress(KEY_START, start)) {
|
||||
std::string password = getPasswordInput();
|
||||
if (password != "") {
|
||||
// (7) Connect to the internet
|
||||
linkMobile->callISP(password.c_str());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LinkMobile::State::CALL_ESTABLISHED: {
|
||||
// L = hang up
|
||||
if (didPress(KEY_L, l)) {
|
||||
// (6) Hang up
|
||||
linkMobile->hangUp();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LinkMobile::State::PPP_ACTIVE: {
|
||||
// A = DNS query
|
||||
if (didPress(KEY_A, a) && !waitingDNS) {
|
||||
std::string domain = getDomainInput();
|
||||
if (domain != "") {
|
||||
// (8) Run DNS queries
|
||||
linkMobile->dnsQuery(domain.c_str(), &dnsQuery);
|
||||
waitingDNS = true;
|
||||
}
|
||||
}
|
||||
// L = hang up
|
||||
if (didPress(KEY_L, l)) {
|
||||
// (6) Hang up
|
||||
linkMobile->hangUp();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
}
|
||||
}
|
||||
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void handleP2P() {
|
||||
if (!isConnected) {
|
||||
// First transfer
|
||||
isConnected = true;
|
||||
outgoingData = linkMobile->getRole() == LinkMobile::Role::CALLER
|
||||
? "caller!!!"
|
||||
: "receiver!!!";
|
||||
transfer(dataTransfer, outgoingData, 0xff, true);
|
||||
}
|
||||
|
||||
if (dataTransfer.completed) {
|
||||
// Save a copy of last received data
|
||||
if (dataTransfer.size > 0)
|
||||
lastCompletedTransfer = dataTransfer;
|
||||
dataTransfer.completed = false;
|
||||
}
|
||||
|
||||
if (keys & KEY_A) {
|
||||
// `A` increments the counter
|
||||
counter++;
|
||||
outgoingData =
|
||||
(linkMobile->getRole() == LinkMobile::Role::CALLER ? "caller: "
|
||||
: "receiver: ") +
|
||||
std::to_string(counter);
|
||||
}
|
||||
|
||||
frameCounter++;
|
||||
if (frameCounter >= TRANSFER_FREQUENCY) {
|
||||
// Transfer every N frames
|
||||
frameCounter = 0;
|
||||
transfer(dataTransfer, outgoingData, 0xff, true);
|
||||
}
|
||||
|
||||
if (lastCompletedTransfer.completed) {
|
||||
// Show received data
|
||||
output += "\n\n>> " + std::string(outgoingData);
|
||||
output += "\n<< " + std::string((char*)lastCompletedTransfer.data);
|
||||
// (LinkMobile zero-pads an extra byte, so this is safe)
|
||||
}
|
||||
}
|
||||
|
||||
void handlePPP() {
|
||||
if (!isConnected)
|
||||
isConnected = true;
|
||||
|
||||
if (waitingDNS && dnsQuery.completed) {
|
||||
waitingDNS = false;
|
||||
log("DNS Response:\n " + std::to_string(dnsQuery.ipv4[0]) + "." +
|
||||
std::to_string(dnsQuery.ipv4[1]) + "." +
|
||||
std::to_string(dnsQuery.ipv4[2]) + "." +
|
||||
std::to_string(dnsQuery.ipv4[3]) + "\n\n" +
|
||||
(dnsQuery.success ? "OK!\nLet's connect to it on TCP 80!"
|
||||
: "DNS query failed!"));
|
||||
waitForA();
|
||||
if (!dnsQuery.success)
|
||||
return;
|
||||
|
||||
// (9) Open connections
|
||||
log("Connecting...");
|
||||
LinkMobile::OpenConn openConn;
|
||||
linkMobile->openConnection(dnsQuery.ipv4, 80,
|
||||
LinkMobile::ConnectionType::TCP, &openConn);
|
||||
if (!linkMobile->waitFor(&openConn)) {
|
||||
log("Connection failed!");
|
||||
waitForA();
|
||||
return;
|
||||
}
|
||||
|
||||
// HTTP request
|
||||
|
||||
LinkMobile::DataTransfer http;
|
||||
std::string request =
|
||||
std::string("GET / HTTP/1.1\r\nHost: ") + selectedDomain + "\r\n\r\n";
|
||||
std::string output = "";
|
||||
u32 chunk = 1;
|
||||
u32 retry = 1;
|
||||
do {
|
||||
log("Downloading... (" + std::to_string(chunk) + ", " +
|
||||
std::to_string(retry) + ")\n (hold START = close conn)\n\n" + output);
|
||||
|
||||
if (didPress(KEY_START, start)) {
|
||||
log("Closing...");
|
||||
LinkMobile::CloseConn closeConn;
|
||||
linkMobile->closeConnection(
|
||||
openConn.connectionId, LinkMobile::ConnectionType::TCP, &closeConn);
|
||||
linkMobile->waitFor(&closeConn);
|
||||
return;
|
||||
}
|
||||
|
||||
transfer(http, request, openConn.connectionId);
|
||||
if (!linkMobile->waitFor(&http)) {
|
||||
log("Connection closed:\n " + std::to_string(chunk) + " packets!\n\n" +
|
||||
output);
|
||||
waitForA();
|
||||
return;
|
||||
}
|
||||
|
||||
if (http.size > 0) {
|
||||
chunk++;
|
||||
output += std::string((char*)http.data);
|
||||
// (LinkMobile zero-pads an extra byte, so this is safe)
|
||||
}
|
||||
|
||||
http = {};
|
||||
request = "";
|
||||
retry++;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
output += waitingDNS ? "\n\nWaiting DNS..." : "";
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
isConnected = false;
|
||||
dataTransfer = {};
|
||||
lastCompletedTransfer = {};
|
||||
dnsQuery = {};
|
||||
waitingDNS = false;
|
||||
counter = 0;
|
||||
frameCounter = 0;
|
||||
outgoingData = "";
|
||||
}
|
||||
|
||||
void readConfiguration() {
|
||||
LinkMobile::ConfigurationData data;
|
||||
if (!linkMobile->readConfiguration(data))
|
||||
log("Read failed :(");
|
||||
|
||||
log("Magic:\n " + toStr(data.magic, 2) + ", $" +
|
||||
toHex(data.registrationState) + "\nPrimary DNS:\n " +
|
||||
std::to_string(data.primaryDNS[0]) + "." +
|
||||
std::to_string(data.primaryDNS[1]) + "." +
|
||||
std::to_string(data.primaryDNS[2]) + "." +
|
||||
std::to_string(data.primaryDNS[3]) + "\nSecondary DNS:\n " +
|
||||
std::to_string(data.secondaryDNS[0]) + "." +
|
||||
std::to_string(data.secondaryDNS[1]) + "." +
|
||||
std::to_string(data.secondaryDNS[2]) + "." +
|
||||
std::to_string(data.secondaryDNS[3]) + "\nLoginID:\n " +
|
||||
toStr(data.loginId, 10) + "\nEmail:\n " + toStr(data.email, 24) +
|
||||
"\nSMTP Server:\n " + toStr(data.smtpServer, 20) + "\nPOP Server:\n " +
|
||||
toStr(data.popServer, 19) + "\nISP Number #1:\n " +
|
||||
std::string(data._ispNumber1) + "\n\nIs Valid: " +
|
||||
std::to_string(linkMobile->isConfigurationValid()) + "\nMode: " +
|
||||
(linkMobile->getDataSize() == LinkSPI::DataSize::SIZE_32BIT ? "SIO32"
|
||||
: "SIO8"));
|
||||
}
|
||||
|
||||
void printMenu() {
|
||||
auto error = linkMobile->getError();
|
||||
|
||||
if (hasError) {
|
||||
output += getErrorString(error);
|
||||
output += "\n (SELECT = stop)";
|
||||
} else if (linkMobile->getState() == LinkMobile::State::SESSION_ACTIVE) {
|
||||
output += "\nL = Read configuration";
|
||||
output += "\nR = Call someone";
|
||||
output += "\nSTART = Call the ISP";
|
||||
output += "\n\n (A = ok)\n (SELECT = stop)";
|
||||
} else {
|
||||
if (linkMobile->isConnectedP2P()) {
|
||||
output += "\n (A = send)";
|
||||
output += "\n (L = hang up)";
|
||||
} else if (linkMobile->isConnectedPPP()) {
|
||||
output += "\n (A = DNS query)";
|
||||
output += "\n (L = hang up)";
|
||||
}
|
||||
output += "\n (SELECT = stop)";
|
||||
}
|
||||
}
|
||||
|
||||
void transfer(LinkMobile::DataTransfer& dataTransfer,
|
||||
std::string text,
|
||||
u8 connectionId,
|
||||
bool addNullTerminator) {
|
||||
// (5) Send/receive data
|
||||
for (u32 i = 0; i < text.size(); i++)
|
||||
dataTransfer.data[i] = text[i];
|
||||
if (addNullTerminator)
|
||||
dataTransfer.data[text.size()] = '\0';
|
||||
dataTransfer.size = text.size() + addNullTerminator;
|
||||
linkMobile->transfer(dataTransfer, &dataTransfer, connectionId);
|
||||
}
|
||||
|
||||
std::string getNumberInput() {
|
||||
std::vector<std::vector<std::string>> rows;
|
||||
rows.push_back({"1", "2", "3"});
|
||||
rows.push_back({"4", "5", "6"});
|
||||
rows.push_back({"7", "8", "9"});
|
||||
rows.push_back({"*", "0", "#"});
|
||||
std::vector<std::vector<std::string>> altRows;
|
||||
|
||||
return getInput(selectedNumber, LINK_MOBILE_MAX_PHONE_NUMBER_LENGTH,
|
||||
"a number", rows, altRows, {{"localhost", "127000000001"}},
|
||||
"");
|
||||
}
|
||||
|
||||
std::string getPasswordInput() {
|
||||
return getTextInput(selectedPassword, LINK_MOBILE_MAX_PASSWORD_LENGTH,
|
||||
"your password", {{"pass123", "pass123"}});
|
||||
}
|
||||
|
||||
std::string getDomainInput() {
|
||||
return getTextInput(
|
||||
selectedDomain, LINK_MOBILE_MAX_DOMAIN_NAME_LENGTH, "a domain name",
|
||||
{{"something.com", "something.com"}, {"localhost", "localhost"}});
|
||||
}
|
||||
|
||||
std::string getTextInput(std::string& field,
|
||||
u32 maxChars,
|
||||
std::string inputName,
|
||||
std::vector<DefaultValue> defaultValues) {
|
||||
std::vector<std::vector<std::string>> rows;
|
||||
rows.push_back({"a", "b", "c", "d", "e"});
|
||||
rows.push_back({"f", "g", "h", "i", "j"});
|
||||
rows.push_back({"k", "l", "m", "n", "o"});
|
||||
rows.push_back({"p", "q", "r", "s", "t"});
|
||||
rows.push_back({"u", "v", "w", "x", "y"});
|
||||
rows.push_back({"z", "1", "2", "3", "4"});
|
||||
rows.push_back({"5", "6", "7", "8", "9"});
|
||||
rows.push_back({"0", ".", "#", "/", "?"});
|
||||
|
||||
std::vector<std::vector<std::string>> altRows;
|
||||
altRows.push_back({"A", "B", "C", "D", "E"});
|
||||
altRows.push_back({"F", "G", "H", "I", "J"});
|
||||
altRows.push_back({"K", "L", "M", "N", "O"});
|
||||
altRows.push_back({"P", "Q", "R", "S", "T"});
|
||||
altRows.push_back({"U", "V", "W", "X", "Y"});
|
||||
altRows.push_back({"Z", "1", "2", "3", "4"});
|
||||
altRows.push_back({"5", "6", "7", "8", "9"});
|
||||
altRows.push_back({"0", ".", "#", "/", "?"});
|
||||
|
||||
return getInput(field, maxChars, inputName, rows, altRows, defaultValues,
|
||||
"caps lock");
|
||||
}
|
||||
|
||||
std::string getInput(std::string& field,
|
||||
u32 maxChars,
|
||||
std::string inputName,
|
||||
std::vector<std::vector<std::string>> rows,
|
||||
std::vector<std::vector<std::string>> altRows,
|
||||
std::vector<DefaultValue> defaultValues,
|
||||
std::string altName) {
|
||||
VBlankIntrWait();
|
||||
|
||||
int selectedX = 0;
|
||||
int selectedY = 0;
|
||||
int selectedDefaultValue = 0;
|
||||
bool altActive = false;
|
||||
|
||||
while (true) {
|
||||
auto renderRows = altActive ? altRows : rows;
|
||||
|
||||
std::string output = "Type " + inputName + ":\n\n";
|
||||
output += ">> " + field + "\n\n";
|
||||
|
||||
if (didPress(KEY_RIGHT, right)) {
|
||||
selectedX++;
|
||||
if (selectedX >= (int)renderRows[0].size())
|
||||
selectedX = renderRows[0].size() - 1;
|
||||
}
|
||||
if (didPress(KEY_LEFT, left)) {
|
||||
selectedX--;
|
||||
if (selectedX < 0)
|
||||
selectedX = 0;
|
||||
}
|
||||
if (didPress(KEY_UP, up)) {
|
||||
selectedY--;
|
||||
if (selectedY < 0)
|
||||
selectedY = 0;
|
||||
}
|
||||
if (didPress(KEY_DOWN, down)) {
|
||||
selectedY++;
|
||||
if (selectedY >= (int)renderRows.size())
|
||||
selectedY = renderRows.size() - 1;
|
||||
}
|
||||
if (didPress(KEY_B, b)) {
|
||||
if (field.size() > 0)
|
||||
field = field.substr(0, field.size() - 1);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
if (didPress(KEY_A, a)) {
|
||||
if (field.size() < maxChars)
|
||||
field += renderRows[selectedY][selectedX];
|
||||
}
|
||||
if (didPress(KEY_SELECT, select)) {
|
||||
field = defaultValues[selectedDefaultValue].value;
|
||||
selectedDefaultValue = (selectedDefaultValue + 1) % defaultValues.size();
|
||||
}
|
||||
if (didPress(KEY_START, start))
|
||||
return field;
|
||||
if (altName != "" && didPress(KEY_L, l))
|
||||
altActive = !altActive;
|
||||
|
||||
for (int y = 0; y < (int)renderRows.size(); y++) {
|
||||
for (int x = 0; x < (int)renderRows[y].size(); x++) {
|
||||
bool isSelected = selectedX == x && selectedY == y;
|
||||
output += std::string("") + "|" + (isSelected ? "<" : " ") +
|
||||
renderRows[y][x] + (isSelected ? ">" : " ") + "|";
|
||||
output += " ";
|
||||
}
|
||||
output += "\n";
|
||||
}
|
||||
|
||||
output += "\n (B = back)\n (A = select)\n (SELECT = " +
|
||||
defaultValues[selectedDefaultValue].name +
|
||||
")\n (START = confirm)";
|
||||
|
||||
if (altName != "")
|
||||
output += "\n\n (L = " + altName + ")";
|
||||
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
}
|
||||
}
|
||||
|
||||
std::string getStateString(LinkMobile::State state) {
|
||||
switch (state) {
|
||||
case LinkMobile::State::NEEDS_RESET:
|
||||
return "NEEDS_RESET";
|
||||
case LinkMobile::State::PINGING:
|
||||
return "PINGING";
|
||||
case LinkMobile::State::WAITING_TO_START:
|
||||
return "WAITING_TO_START";
|
||||
case LinkMobile::State::STARTING_SESSION:
|
||||
return "STARTING_SESSION";
|
||||
case LinkMobile::State::ACTIVATING_SIO32:
|
||||
return "ACTIVATING_SIO32";
|
||||
case LinkMobile::State::WAITING_32BIT_SWITCH:
|
||||
return "WAITING_32BIT_SWITCH";
|
||||
case LinkMobile::State::READING_CONFIGURATION:
|
||||
return "READING_CONFIGURATION";
|
||||
case LinkMobile::State::SESSION_ACTIVE:
|
||||
return "SESSION_ACTIVE";
|
||||
case LinkMobile::State::CALL_REQUESTED:
|
||||
return "CALL_REQUESTED";
|
||||
case LinkMobile::State::CALLING:
|
||||
return "CALLING";
|
||||
case LinkMobile::State::CALL_ESTABLISHED:
|
||||
return "CALL_ESTABLISHED";
|
||||
case LinkMobile::State::ISP_CALL_REQUESTED:
|
||||
return "ISP_CALL_REQUESTED";
|
||||
case LinkMobile::State::ISP_CALLING:
|
||||
return "ISP_CALLING";
|
||||
case LinkMobile::State::PPP_LOGIN:
|
||||
return "PPP_LOGIN";
|
||||
case LinkMobile::State::PPP_ACTIVE:
|
||||
return "PPP_ACTIVE";
|
||||
case LinkMobile::State::SHUTDOWN_REQUESTED:
|
||||
return "SHUTDOWN_REQUESTED";
|
||||
case LinkMobile::State::ENDING_SESSION:
|
||||
return "ENDING_SESSION";
|
||||
case LinkMobile::State::WAITING_8BIT_SWITCH:
|
||||
return "WAITING_8BIT_SWITCH";
|
||||
case LinkMobile::State::SHUTDOWN:
|
||||
return "SHUTDOWN";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
std::string getErrorString(LinkMobile::Error error) {
|
||||
return "ERROR"
|
||||
"\n Type: " +
|
||||
getErrorTypeString(error.type) +
|
||||
"\n State: " + getStateString(error.state) +
|
||||
"\n CmdID: " + std::string(error.cmdIsSending ? ">" : "<") + "$" +
|
||||
toHex(error.cmdId) +
|
||||
"\n CmdResult: " + getResultString(error.cmdResult) +
|
||||
"\n CmdErrorCode: " + std::to_string(error.cmdErrorCode) +
|
||||
"\n ReqType: " + std::to_string(error.reqType) + "\n";
|
||||
}
|
||||
|
||||
std::string getErrorTypeString(LinkMobile::Error::Type errorType) {
|
||||
switch (errorType) {
|
||||
case LinkMobile::Error::Type::ADAPTER_NOT_CONNECTED:
|
||||
return "ADAPTER_NOT_CONNECTED";
|
||||
case LinkMobile::Error::Type::PPP_LOGIN_FAILED:
|
||||
return "PPP_LOGIN_FAILED";
|
||||
case LinkMobile::Error::Type::COMMAND_FAILED:
|
||||
return "COMMAND_FAILED";
|
||||
case LinkMobile::Error::Type::WEIRD_RESPONSE:
|
||||
return "WEIRD_RESPONSE";
|
||||
case LinkMobile::Error::Type::TIMEOUT:
|
||||
return "TIMEOUT";
|
||||
case LinkMobile::Error::Type::WTF:
|
||||
return "WTF";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
std::string getResultString(LinkMobile::CommandResult cmdResult) {
|
||||
switch (cmdResult) {
|
||||
case LinkMobile::CommandResult::PENDING:
|
||||
return "PENDING";
|
||||
case LinkMobile::CommandResult::SUCCESS:
|
||||
return "SUCCESS";
|
||||
case LinkMobile::CommandResult::INVALID_DEVICE_ID:
|
||||
return "INVALID_DEVICE_ID";
|
||||
case LinkMobile::CommandResult::INVALID_COMMAND_ACK:
|
||||
return "INVALID_COMMAND_ACK";
|
||||
case LinkMobile::CommandResult::INVALID_MAGIC_BYTES:
|
||||
return "INVALID_MAGIC_BYTES";
|
||||
case LinkMobile::CommandResult::WEIRD_DATA_SIZE:
|
||||
return "WEIRD_DATA_SIZE";
|
||||
case LinkMobile::CommandResult::WRONG_CHECKSUM:
|
||||
return "WRONG_CHECKSUM";
|
||||
case LinkMobile::CommandResult::ERROR_CODE:
|
||||
return "ERROR_CODE";
|
||||
case LinkMobile::CommandResult::WEIRD_ERROR_CODE:
|
||||
return "WEIRD_ERROR_CODE";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
void log(std::string text) {
|
||||
if (linkMobile != nullptr)
|
||||
VBlankIntrWait();
|
||||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
|
||||
std::string toStr(char* chars, int size) {
|
||||
char copiedChars[255];
|
||||
for (int i = 0; i < size; i++)
|
||||
copiedChars[i] = chars[i];
|
||||
copiedChars[size] = '\0';
|
||||
return std::string(copiedChars);
|
||||
}
|
||||
|
||||
void wait(u32 verticalLines) {
|
||||
u32 count = 0;
|
||||
u32 vCount = REG_VCOUNT;
|
||||
|
||||
while (count < verticalLines) {
|
||||
if (REG_VCOUNT != vCount) {
|
||||
count++;
|
||||
vCount = REG_VCOUNT;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bool didPress(u16 key, bool& pressed) {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
bool isPressedNow = false;
|
||||
if ((keys & key) && !pressed) {
|
||||
pressed = true;
|
||||
isPressedNow = true;
|
||||
}
|
||||
if (pressed && !(keys & key))
|
||||
pressed = false;
|
||||
return isPressedNow;
|
||||
}
|
||||
|
||||
void waitForA() {
|
||||
while (!didPress(KEY_A, a))
|
||||
;
|
||||
}
|
||||
|
||||
template <typename I>
|
||||
[[nodiscard]] std::string toHex(I w, size_t hex_len) {
|
||||
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;
|
||||
}
|
||||
54
examples/LinkMobile_demo/src/main.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#ifndef MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include "../../../lib/LinkMobile.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct DefaultValue {
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
void handleP2P();
|
||||
void handlePPP();
|
||||
void cleanup();
|
||||
void readConfiguration();
|
||||
|
||||
void printMenu();
|
||||
void transfer(LinkMobile::DataTransfer& dataTransfer,
|
||||
std::string text,
|
||||
unsigned char connectionId,
|
||||
bool addNullTerminator = false);
|
||||
|
||||
std::string getNumberInput();
|
||||
std::string getPasswordInput();
|
||||
std::string getDomainInput();
|
||||
std::string getTextInput(std::string& field,
|
||||
unsigned int maxChars,
|
||||
std::string inputName,
|
||||
std::vector<DefaultValue> defaultValues);
|
||||
std::string getInput(std::string& field,
|
||||
unsigned int maxChars,
|
||||
std::string inputName,
|
||||
std::vector<std::vector<std::string>> rows,
|
||||
std::vector<std::vector<std::string>> altRows,
|
||||
std::vector<DefaultValue> defaultValues,
|
||||
std::string altName);
|
||||
|
||||
std::string getStateString(LinkMobile::State state);
|
||||
std::string getErrorString(LinkMobile::Error error);
|
||||
std::string getErrorTypeString(LinkMobile::Error::Type errorType);
|
||||
std::string getResultString(LinkMobile::CommandResult cmdResult);
|
||||
|
||||
void log(std::string text);
|
||||
std::string toStr(char* chars, int size);
|
||||
void wait(unsigned int verticalLines);
|
||||
bool didPress(unsigned short key, bool& pressed);
|
||||
void waitForA();
|
||||
|
||||
template <typename I>
|
||||
[[nodiscard]] std::string toHex(I w, size_t hex_len = sizeof(I) << 1);
|
||||
|
||||
#endif // MAIN_H
|
||||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -37,11 +37,13 @@ int main() {
|
|||
init();
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkPS2Keyboard_demo (v6.3.0)\n\n";
|
||||
std::string output = "LinkPS2Keyboard_demo (v7.0.0)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkPS2Keyboard->isActive()) {
|
||||
output += "Press A to read keyboard input";
|
||||
output +=
|
||||
"Press A to read keyboard input\n"
|
||||
"Press B to clear logs";
|
||||
|
||||
if (keys & KEY_A) {
|
||||
// (3) Initialize the library
|
||||
|
|
@ -51,6 +53,10 @@ int main() {
|
|||
continue;
|
||||
}
|
||||
} else {
|
||||
if (keys & KEY_B) {
|
||||
scanCodes = "";
|
||||
irqs = 0;
|
||||
}
|
||||
output += std::to_string(irqs) + " - " + scanCodes;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkPS2Mouse.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkPS2Mouse.hpp"
|
||||
|
||||
void log(std::string text);
|
||||
inline void VBLANK() {}
|
||||
inline void TIMER() {}
|
||||
|
|
@ -12,6 +12,10 @@ inline void TIMER() {}
|
|||
// (1) Create a LinkPS2Mouse instance
|
||||
LinkPS2Mouse* linkPS2Mouse = new LinkPS2Mouse(2);
|
||||
|
||||
inline void KEYPAD() {
|
||||
SoftReset();
|
||||
}
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
|
|
@ -22,17 +26,24 @@ void init() {
|
|||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_TIMER2, TIMER);
|
||||
interrupt_enable(INTR_TIMER2);
|
||||
|
||||
// Interrupt to handle B event (to reset)
|
||||
REG_KEYCNT = 0b10 | (1 << 14);
|
||||
interrupt_set_handler(INTR_KEYPAD, KEYPAD);
|
||||
interrupt_enable(INTR_KEYPAD);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkPS2Mouse_demo (v6.3.0)\n\n";
|
||||
std::string output = "LinkPS2Mouse_demo (v7.0.0)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkPS2Mouse->isActive()) {
|
||||
output += "Press A to read mouse input";
|
||||
output +=
|
||||
"Press A to read mouse input\n"
|
||||
"Press B to cancel";
|
||||
|
||||
if (keys & KEY_A) {
|
||||
// (3) Initialize the library
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkRawCable.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkRawCable.hpp"
|
||||
|
||||
void log(std::string text);
|
||||
void wait(u32 verticalLines);
|
||||
inline void VBLANK() {}
|
||||
|
|
@ -33,7 +33,7 @@ int main() {
|
|||
u16 prevKeys = 0;
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkRawCable_demo (v6.3.0)\n\n";
|
||||
std::string output = "LinkRawCable_demo (v7.0.0)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkRawCable->isActive()) {
|
||||
|
|
@ -131,4 +131,4 @@ void wait(u32 verticalLines) {
|
|||
vCount = REG_VCOUNT;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,6 @@ BUILD := build
|
|||
SRCDIRS := src ../_lib ../../lib \
|
||||
src/scenes \
|
||||
src/utils
|
||||
|
||||
DATADIRS :=
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
|
||||
|
|
@ -156,10 +155,11 @@ CFLAGS += -Wall
|
|||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "../../../lib/LinkRawWireless.hpp"
|
||||
|
||||
#include <libgba-sprite-engine/gba_engine.h>
|
||||
#include <tonc.h>
|
||||
#include "../../../lib/LinkRawWireless.hpp"
|
||||
#include "../../_lib/interrupt.h"
|
||||
#include "scenes/DebugScene.h"
|
||||
#include "utils/SceneUtils.h"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
#include "../../../../lib/LinkRawWireless.hpp"
|
||||
|
||||
#include "DebugScene.h"
|
||||
|
||||
#include <libgba-sprite-engine/background/text_stream.h>
|
||||
|
|
@ -5,7 +7,6 @@
|
|||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include "../../../../lib/LinkRawWireless.hpp"
|
||||
#include "utils/InputHandler.h"
|
||||
#include "utils/SceneUtils.h"
|
||||
|
||||
|
|
@ -139,7 +140,7 @@ void DebugScene::load() {
|
|||
|
||||
log("---");
|
||||
log("LinkRawWireless demo");
|
||||
log(" (v6.3.0)");
|
||||
log(" (v7.0.0)");
|
||||
log("");
|
||||
log("START: reset wireless adapter");
|
||||
log("A: send command");
|
||||
|
|
@ -771,7 +772,7 @@ genericWait:
|
|||
}
|
||||
|
||||
int DebugScene::selectServerId() {
|
||||
switch (selectOption("Which server id?", std::vector<std::string>{
|
||||
switch (selectOption("Which server ID?", std::vector<std::string>{
|
||||
"<first>", "<second>", "<third>",
|
||||
"<fourth>", "<pick>"})) {
|
||||
case 0: {
|
||||
|
|
|
|||
|
|
@ -36,4 +36,4 @@ class InputHandler {
|
|||
bool isWaiting = false;
|
||||
};
|
||||
|
||||
#endif // INPUT_HANDLER_H
|
||||
#endif // INPUT_HANDLER_H
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkSPI.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkSPI.hpp"
|
||||
|
||||
void log(std::string text);
|
||||
void wait(u32 verticalLines);
|
||||
inline void VBLANK() {}
|
||||
|
|
@ -32,7 +32,7 @@ int main() {
|
|||
u32 counter = 0;
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkSPI_demo (v6.3.0)\n\n";
|
||||
std::string output = "LinkSPI_demo (v7.0.0)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkSPI->isActive()) {
|
||||
|
|
@ -42,14 +42,17 @@ int main() {
|
|||
output += "\n(stop: press L+R)\n";
|
||||
output += "(hold A on start for async)\n";
|
||||
output += "(hold B on start for waitMode)\n";
|
||||
output += "(hold UP for 8-bit mode)\n";
|
||||
output +=
|
||||
"\n\n\n\n\n\n\n\n\n\n\n\n[!] to test this demo...\n "
|
||||
"\n\n\n\n\n\n\n\n\n[!] to test this demo...\n "
|
||||
"...use a GBC Link Cable!";
|
||||
|
||||
if ((keys & KEY_START) | (keys & KEY_SELECT)) {
|
||||
// (3) Initialize the library
|
||||
linkSPI->activate((keys & KEY_START) ? LinkSPI::Mode::MASTER_256KBPS
|
||||
: LinkSPI::Mode::SLAVE);
|
||||
: LinkSPI::Mode::SLAVE,
|
||||
(keys & KEY_UP) ? LinkSPI::DataSize::SIZE_8BIT
|
||||
: LinkSPI::DataSize::SIZE_32BIT);
|
||||
linkSPI->setWaitModeActive(keys &
|
||||
KEY_B); // see `waitMode` in README.md
|
||||
if (keys & KEY_A)
|
||||
|
|
@ -123,4 +126,4 @@ void wait(u32 verticalLines) {
|
|||
vCount = REG_VCOUNT;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
const { SerialPort, ReadlineParser } = require('serialport') // "^12.0.0"
|
||||
// node v14
|
||||
const { SerialPort, ReadlineParser } = require("serialport"); // "^12.0.0"
|
||||
|
||||
var serialPort = new SerialPort({
|
||||
path: "COM9", // (*nix: /dev/ttyACMX, Windows: COMX)
|
||||
baudRate: 9600
|
||||
baudRate: 9600,
|
||||
});
|
||||
const parser = serialPort.pipe(new ReadlineParser());
|
||||
|
||||
parser.on('data', (it) => console.log(it))
|
||||
parser.on("data", (it) => console.log(it));
|
||||
|
||||
setInterval(() => {
|
||||
serialPort.write('<< node\n')
|
||||
}, 1000);
|
||||
serialPort.write("<< node\n");
|
||||
}, 1000);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkUART.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkUART.hpp"
|
||||
|
||||
void log(std::string text);
|
||||
inline void VBLANK() {}
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ int main() {
|
|||
bool firstTransfer = false;
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkUART_demo (v6.3.0)\n\n";
|
||||
std::string output = "LinkUART_demo (v7.0.0)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkUART->isActive()) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// BASIC:
|
||||
// This example sends the pressed buttons to other players.
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkUniversal.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
void log(std::string text);
|
||||
void waitFor(u16 key);
|
||||
|
||||
LinkUniversal* linkUniversal = NULL;
|
||||
LinkUniversal* linkUniversal = nullptr;
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
|
|
@ -21,11 +21,13 @@ void init() {
|
|||
int main() {
|
||||
init();
|
||||
|
||||
log("LinkUniversal_basic (v6.3.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)");
|
||||
log("LinkUniversal_basic (v7.0.0)\n\n\n"
|
||||
"Press A to start\n\n\n"
|
||||
"hold LEFT on start:\n -> force cable\n\n"
|
||||
"hold RIGHT on start:\n -> force wireless\n\n"
|
||||
"hold UP on start:\n -> force wireless server\n\n"
|
||||
"hold DOWN on start:\n -> force wireless client\n\n"
|
||||
"hold B on start:\n -> set 2 players (wireless)");
|
||||
waitFor(KEY_A);
|
||||
u16 initialKeys = ~REG_KEYS & KEY_ANY;
|
||||
bool forceCable = initialKeys & KEY_LEFT;
|
||||
|
|
@ -41,22 +43,20 @@ int main() {
|
|||
u32 maxPlayers = (initialKeys & KEY_B) ? 2 : LINK_UNIVERSAL_MAX_PLAYERS;
|
||||
|
||||
// (1) Create a LinkUniversal instance
|
||||
linkUniversal = new LinkUniversal(
|
||||
protocol, "LinkUNI",
|
||||
(LinkUniversal::CableOptions){
|
||||
.baudRate = LinkCable::BAUD_RATE_1,
|
||||
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
|
||||
.remoteTimeout = LINK_CABLE_DEFAULT_REMOTE_TIMEOUT,
|
||||
.interval = LINK_CABLE_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
|
||||
(LinkUniversal::WirelessOptions){
|
||||
.retransmission = true,
|
||||
.maxPlayers = maxPlayers,
|
||||
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
|
||||
.remoteTimeout = LINK_WIRELESS_DEFAULT_REMOTE_TIMEOUT,
|
||||
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID,
|
||||
.asyncACKTimerId = LINK_WIRELESS_DEFAULT_ASYNC_ACK_TIMER_ID});
|
||||
linkUniversal =
|
||||
new LinkUniversal(protocol, "LinkUNI",
|
||||
(LinkUniversal::CableOptions){
|
||||
.baudRate = LinkCable::BAUD_RATE_1,
|
||||
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
|
||||
.interval = LINK_CABLE_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
|
||||
(LinkUniversal::WirelessOptions){
|
||||
.retransmission = true,
|
||||
.maxPlayers = maxPlayers,
|
||||
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
|
||||
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID},
|
||||
__qran_seed);
|
||||
|
||||
// (2) Add the required interrupt service routines
|
||||
interrupt_init();
|
||||
|
|
@ -131,4 +131,4 @@ void waitFor(u16 key) {
|
|||
do {
|
||||
keys = ~REG_KEYS & KEY_ANY;
|
||||
} while (!(keys & key));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
- Reuse code from `examples/LinkCable_full`
|
||||
- Uncomment `USE_LINK_UNIVERSAL` in `src/main.h`
|
||||
- Compile!
|
||||
- Compile!
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
- Reuse code from `examples/LinkCable_stress`
|
||||
- Uncomment `USE_LINK_UNIVERSAL` in `src/main.h`
|
||||
- Compile!
|
||||
- Compile!
|
||||
|
|
|
|||
|
|
@ -123,11 +123,9 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba -lgba-sprite-engine
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib \
|
||||
SRCDIRS := src ../_lib ../../lib ../_lib/libgbfs \
|
||||
src/scenes \
|
||||
src/utils \
|
||||
src/utils/gbfs
|
||||
|
||||
src/utils
|
||||
DATADIRS :=
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
|
||||
|
|
@ -157,10 +155,11 @@ CFLAGS += -Wall
|
|||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -283,7 +282,7 @@ endif # End BUILD switch
|
|||
rebuild: clean $(BUILD) package
|
||||
|
||||
package: $(BUILD)
|
||||
./scripts/package.sh
|
||||
../gbfs.sh "$(TARGET).gba" content.gbfs "$(TARGET).out.gba"
|
||||
|
||||
start: package
|
||||
start "$(TARGET).out.gba"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "../../../lib/LinkWirelessMultiboot.hpp"
|
||||
|
||||
#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"
|
||||
|
|
@ -16,8 +17,6 @@ LinkWirelessMultiboot* linkWirelessMultiboot = new LinkWirelessMultiboot();
|
|||
int main() {
|
||||
setUpInterrupts();
|
||||
|
||||
REG_WAITCNT = 0x4317; // (3,1 waitstates, prefetch ON)
|
||||
|
||||
engine->setScene(multibootScene.get());
|
||||
|
||||
while (true) {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
// (0) Include the header
|
||||
#include "../../../../lib/LinkWirelessMultiboot.hpp"
|
||||
|
||||
#include "MultibootScene.h"
|
||||
|
||||
#include <libgba-sprite-engine/background/text_stream.h>
|
||||
#include <string.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"
|
||||
#include "../../_lib/libgbfs/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) {}
|
||||
|
||||
|
|
@ -23,6 +24,10 @@ 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> leftHandler =
|
||||
std::unique_ptr<InputHandler>(new InputHandler());
|
||||
static std::unique_ptr<InputHandler> rightHandler =
|
||||
std::unique_ptr<InputHandler>(new InputHandler());
|
||||
static std::unique_ptr<InputHandler> upHandler =
|
||||
std::unique_ptr<InputHandler>(new InputHandler());
|
||||
static std::unique_ptr<InputHandler> downHandler =
|
||||
|
|
@ -33,9 +38,12 @@ 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::unique_ptr<InputHandler> startHandler =
|
||||
std::unique_ptr<InputHandler>(new InputHandler());
|
||||
|
||||
static std::vector<std::string> logLines;
|
||||
static u32 currentLogLine = 0;
|
||||
static u32 selectedFile = 0;
|
||||
static u32 players = 5;
|
||||
|
||||
#define MAX_LINES 20
|
||||
|
|
@ -109,6 +117,59 @@ void log(std::string string) {
|
|||
scrollPageDown();
|
||||
}
|
||||
|
||||
void printFile() {
|
||||
char name[32];
|
||||
gbfs_get_nth_obj(fs, selectedFile, name, NULL);
|
||||
for (u32 i = 0; i < 32; i++) {
|
||||
if (name[i] == '.') {
|
||||
name[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
log("! selected file: " + std::to_string(selectedFile + 1) + "/" +
|
||||
std::to_string(fs->dir_nmemb));
|
||||
log(" " + std::string(name));
|
||||
}
|
||||
|
||||
void selectLeft() {
|
||||
if (selectedFile == 0)
|
||||
return printFile();
|
||||
selectedFile--;
|
||||
printFile();
|
||||
}
|
||||
|
||||
void selectRight() {
|
||||
if ((int)selectedFile >= fs->dir_nmemb - 1)
|
||||
return printFile();
|
||||
selectedFile++;
|
||||
printFile();
|
||||
}
|
||||
|
||||
void launchROM() {
|
||||
log("Launching...");
|
||||
VBlankIntrWait();
|
||||
|
||||
REG_IME = 0;
|
||||
|
||||
u32 fileLength;
|
||||
const u8* romToSend =
|
||||
(const u8*)gbfs_get_nth_obj(fs, selectedFile, NULL, &fileLength);
|
||||
|
||||
void* EWRAM = (void*)0x02000000;
|
||||
memcpy(EWRAM, romToSend, fileLength);
|
||||
|
||||
asm volatile(
|
||||
"mov r0, %0\n"
|
||||
"bx r0\n"
|
||||
:
|
||||
: "r"(EWRAM)
|
||||
: "r0");
|
||||
|
||||
while (true)
|
||||
;
|
||||
}
|
||||
|
||||
std::vector<Background*> MultibootScene::backgrounds() {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -125,6 +186,7 @@ void MultibootScene::load() {
|
|||
#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);
|
||||
|
|
@ -133,27 +195,31 @@ void MultibootScene::load() {
|
|||
|
||||
log("---");
|
||||
log("LinkWirelessMultiboot demo");
|
||||
log(" (v6.3.0)");
|
||||
log(" (v7.0.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));
|
||||
} else if (gbfs_get_nth_obj(fs, 0, NULL, NULL) == NULL) {
|
||||
log("! No files found (GBFS)");
|
||||
while (true)
|
||||
;
|
||||
}
|
||||
log("A: send ROM");
|
||||
log("B: toggle players");
|
||||
log("B: launch ROM");
|
||||
log("START: toggle players");
|
||||
log("LEFT/RIGHT: select ROM");
|
||||
log("UP/DOWN: scroll up/down");
|
||||
log("L/R: scroll page up/down");
|
||||
log("UP+L/DOWN+R: scroll to top/bottom");
|
||||
log("UP+L/DOWN+R: scroll top/bottom");
|
||||
log("L+R: cancel transfer");
|
||||
log("SELECT: clear");
|
||||
log("---");
|
||||
log("");
|
||||
togglePlayers();
|
||||
selectedFile = fs->dir_nmemb - 1;
|
||||
printFile();
|
||||
}
|
||||
|
||||
void MultibootScene::tick(u16 keys) {
|
||||
|
|
@ -171,26 +237,34 @@ void MultibootScene::tick(u16 keys) {
|
|||
void MultibootScene::processKeys(u16 keys) {
|
||||
aHandler->setIsPressed(keys & KEY_A);
|
||||
bHandler->setIsPressed(keys & KEY_B);
|
||||
leftHandler->setIsPressed(keys & KEY_LEFT);
|
||||
rightHandler->setIsPressed(keys & KEY_RIGHT);
|
||||
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);
|
||||
startHandler->setIsPressed(keys & KEY_START);
|
||||
}
|
||||
|
||||
void MultibootScene::processButtons() {
|
||||
if (bHandler->hasBeenPressedNow())
|
||||
if (startHandler->hasBeenPressedNow())
|
||||
togglePlayers();
|
||||
|
||||
if (aHandler->hasBeenPressedNow()) {
|
||||
u32 fileLength;
|
||||
const u8* romToSend =
|
||||
(const u8*)gbfs_get_obj(fs, ROM_FILE_NAME, &fileLength);
|
||||
(const u8*)gbfs_get_nth_obj(fs, selectedFile, NULL, &fileLength);
|
||||
|
||||
clear();
|
||||
printFile();
|
||||
|
||||
// (2) Send the ROM
|
||||
auto result = linkWirelessMultiboot->sendRom(
|
||||
romToSend, fileLength, "Multiboot", "Test", 0xffff, players,
|
||||
[](LinkWirelessMultiboot::MultibootProgress progress) {
|
||||
return false;
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
return (keys & KEY_L) && (keys & KEY_R);
|
||||
});
|
||||
log("-> result: " + std::to_string(result));
|
||||
print();
|
||||
|
|
@ -210,6 +284,12 @@ void MultibootScene::processButtons() {
|
|||
scrollPageDown();
|
||||
}
|
||||
|
||||
if (leftHandler->hasBeenPressedNow())
|
||||
selectLeft();
|
||||
|
||||
if (rightHandler->hasBeenPressedNow())
|
||||
selectRight();
|
||||
|
||||
if (upHandler->getIsPressed())
|
||||
scrollBack();
|
||||
|
||||
|
|
@ -218,6 +298,9 @@ void MultibootScene::processButtons() {
|
|||
|
||||
if (selectHandler->hasBeenPressedNow())
|
||||
clear();
|
||||
|
||||
if (bHandler->hasBeenPressedNow())
|
||||
launchROM();
|
||||
}
|
||||
|
||||
void MultibootScene::togglePlayers() {
|
||||
|
|
|
|||
|
|
@ -36,4 +36,4 @@ class InputHandler {
|
|||
bool isWaiting = false;
|
||||
};
|
||||
|
||||
#endif // INPUT_HANDLER_H
|
||||
#endif // INPUT_HANDLER_H
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export TONCLIB := ${DEVKITPRO}/libtonc
|
|||
|
||||
# === TONC RULES ======================================================
|
||||
#
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# Yes, this is almost, but not quite, completely like to
|
||||
# DKP's base_rules and gba_rules
|
||||
#
|
||||
|
||||
|
|
@ -53,7 +53,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
|
||||
%.elf :
|
||||
@echo Linking cartridge
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(LD) -specs=gba.specs $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@
|
||||
$(NM) -Sn $@ > $(basename $(notdir $@)).map
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
|
@ -69,7 +69,7 @@ export OBJCOPY := $(PREFIX)objcopy
|
|||
%.iwram.o : %.iwram.cpp
|
||||
@echo $(notdir $<)
|
||||
$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(IARCH) -c $< -o $@
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
%.iwram.o : %.iwram.c
|
||||
@echo $(notdir $<)
|
||||
|
|
@ -148,8 +148,8 @@ 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
|
||||
# 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 ---
|
||||
|
|
@ -160,15 +160,16 @@ IARCH := -mthumb-interwork -marm -mlong-calls
|
|||
|
||||
# --- Main flags ---
|
||||
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -O2
|
||||
CFLAGS := -mcpu=arm7tdmi -mtune=arm7tdmi -Ofast
|
||||
CFLAGS += -Wall
|
||||
CFLAGS += $(INCLUDE)
|
||||
CFLAGS += -ffast-math -fno-strict-aliasing
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
USERFLAGS ?=
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=c++17 -DLINK_DEVELOPMENT $(USERFLAGS)
|
||||
|
||||
ASFLAGS := $(ARCH) $(INCLUDE)
|
||||
LDFLAGS := $(ARCH) -Wl,-Map,$(PROJ).map
|
||||
LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
|
||||
|
||||
# --- switched additions ----------------------------------------------
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ endif
|
|||
|
||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||
|
||||
# Still in main dir:
|
||||
# Still in main dir:
|
||||
# * Define/export some extra variables
|
||||
# * Invoke this file again from the build dir
|
||||
# PONDER: what happens if BUILD == "" ?
|
||||
|
|
@ -238,7 +239,7 @@ export OFILES := $(addsuffix .o, $(BINFILES)) \
|
|||
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 ---
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkWireless.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkWireless.hpp"
|
||||
|
||||
#ifdef PROFILING_ENABLED
|
||||
#include <regex>
|
||||
#endif
|
||||
|
|
@ -27,12 +28,13 @@ void connect();
|
|||
void messageLoop();
|
||||
void log(std::string text);
|
||||
void waitFor(u16 key);
|
||||
bool didPress(u16 key, bool& pressed);
|
||||
void wait(u32 verticalLines);
|
||||
void hang();
|
||||
|
||||
LinkWireless::Error lastError;
|
||||
LinkWireless* linkWireless = NULL;
|
||||
bool forwarding, retransmission, asyncACK;
|
||||
LinkWireless* linkWireless = nullptr;
|
||||
bool forwarding, retransmission;
|
||||
u32 maxPlayers;
|
||||
|
||||
void init() {
|
||||
|
|
@ -43,44 +45,51 @@ void init() {
|
|||
int main() {
|
||||
init();
|
||||
|
||||
bool firstTime = true;
|
||||
std::string buildSettings = "";
|
||||
#ifdef LINK_WIRELESS_PUT_ISR_IN_IWRAM
|
||||
buildSettings += " + irq_iwram\n";
|
||||
#endif
|
||||
#ifdef LINK_WIRELESS_ENABLE_NESTED_IRQ
|
||||
buildSettings += " + irq_nested\n";
|
||||
#endif
|
||||
#ifdef LINK_WIRELESS_USE_SEND_RECEIVE_LATCH
|
||||
buildSettings += " + s/r_latch\n";
|
||||
#endif
|
||||
#ifdef LINK_WIRELESS_TWO_PLAYERS_ONLY
|
||||
buildSettings += " + 2players\n";
|
||||
#endif
|
||||
#ifdef PROFILING_ENABLED
|
||||
buildSettings += " + profiler\n";
|
||||
#endif
|
||||
|
||||
start:
|
||||
// Options
|
||||
log("LinkWireless_demo (v6.3.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");
|
||||
log("LinkWireless_demo (v7.0.0)\n" + buildSettings +
|
||||
"\n"
|
||||
"Press A to start\n\n"
|
||||
"hold LEFT on start:\n -> disable forwarding\n\n"
|
||||
"hold UP on start:\n -> disable retransmission\n\n"
|
||||
"hold B on start:\n -> set 2 players");
|
||||
waitFor(KEY_A);
|
||||
u16 initialKeys = ~REG_KEYS & KEY_ANY;
|
||||
forwarding = !(initialKeys & KEY_LEFT);
|
||||
retransmission = !(initialKeys & KEY_UP);
|
||||
maxPlayers = (initialKeys & KEY_B) ? 2 : LINK_WIRELESS_MAX_PLAYERS;
|
||||
asyncACK = initialKeys & KEY_START;
|
||||
|
||||
// (1) Create a LinkWireless instance
|
||||
linkWireless = new LinkWireless(
|
||||
forwarding, retransmission, maxPlayers, LINK_WIRELESS_DEFAULT_TIMEOUT,
|
||||
LINK_WIRELESS_DEFAULT_REMOTE_TIMEOUT, LINK_WIRELESS_DEFAULT_INTERVAL,
|
||||
LINK_WIRELESS_DEFAULT_SEND_TIMER_ID, asyncACK ? 0 : -1);
|
||||
LINK_WIRELESS_DEFAULT_INTERVAL, LINK_WIRELESS_DEFAULT_SEND_TIMER_ID);
|
||||
// linkWireless->debug = [](std::string str) { log(str); };
|
||||
|
||||
if (firstTime) {
|
||||
// (2) Add the required interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_WIRELESS_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_WIRELESS_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_WIRELESS_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
|
||||
// (only required when using async ACK)
|
||||
interrupt_set_handler(INTR_TIMER0, LINK_WIRELESS_ISR_ACK_TIMER);
|
||||
interrupt_enable(INTR_TIMER0);
|
||||
|
||||
firstTime = false;
|
||||
}
|
||||
// (2) Add the required interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_WIRELESS_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_WIRELESS_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_WIRELESS_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
|
||||
// (3) Initialize the library
|
||||
linkWireless->activate();
|
||||
|
|
@ -98,40 +107,31 @@ start:
|
|||
"(SELECT = cancel)\n (START = activate)\n\n-> forwarding: " +
|
||||
(forwarding ? "ON" : "OFF") +
|
||||
"\n-> retransmission: " + (retransmission ? "ON" : "OFF") +
|
||||
"\n-> max players: " + std::to_string(maxPlayers) +
|
||||
"\n-> async ACK: " + (asyncACK ? "ON" : "OFF"));
|
||||
"\n-> max players: " + std::to_string(maxPlayers));
|
||||
|
||||
// SELECT = back
|
||||
if (keys & KEY_SELECT) {
|
||||
linkWireless->deactivate();
|
||||
interrupt_disable(INTR_VBLANK);
|
||||
interrupt_disable(INTR_SERIAL);
|
||||
interrupt_disable(INTR_TIMER3);
|
||||
interrupt_disable(INTR_TIMER0);
|
||||
delete linkWireless;
|
||||
linkWireless = NULL;
|
||||
linkWireless = nullptr;
|
||||
goto start;
|
||||
}
|
||||
|
||||
// START = Activate
|
||||
if ((keys & KEY_START) && !activating) {
|
||||
activating = true;
|
||||
if (didPress(KEY_START, activating))
|
||||
activate();
|
||||
}
|
||||
if (activating && !(keys & KEY_START))
|
||||
activating = false;
|
||||
|
||||
// L = Serve
|
||||
if ((keys & KEY_L) && !serving) {
|
||||
serving = true;
|
||||
if (didPress(KEY_L, serving))
|
||||
serve();
|
||||
}
|
||||
if (serving && !(keys & KEY_L))
|
||||
serving = false;
|
||||
|
||||
// R = Connect
|
||||
if (!connecting && (keys & KEY_R)) {
|
||||
connecting = true;
|
||||
if (didPress(KEY_R, connecting))
|
||||
connect();
|
||||
}
|
||||
if (connecting && !(keys & KEY_R))
|
||||
connecting = false;
|
||||
|
||||
VBlankIntrWait();
|
||||
}
|
||||
|
|
@ -294,6 +294,10 @@ void messageLoop() {
|
|||
u16 newValue = counters[linkWireless->currentPlayerId()] + 1;
|
||||
bool success = linkWireless->send(newValue);
|
||||
|
||||
#ifdef LINK_WIRELESS_TWO_PLAYERS_ONLY
|
||||
linkWireless->QUICK_SEND = newValue % 32;
|
||||
#endif
|
||||
|
||||
if (success) {
|
||||
counters[linkWireless->currentPlayerId()] = newValue;
|
||||
} else {
|
||||
|
|
@ -316,29 +320,27 @@ void messageLoop() {
|
|||
// (7) Receive data
|
||||
LinkWireless::Message messages[LINK_WIRELESS_QUEUE_SIZE];
|
||||
linkWireless->receive(messages);
|
||||
if (messages[0].packetId != LINK_WIRELESS_END) {
|
||||
for (u32 i = 0; i < LINK_WIRELESS_QUEUE_SIZE; i++) {
|
||||
auto message = messages[i];
|
||||
if (message.packetId == LINK_WIRELESS_END)
|
||||
break;
|
||||
for (u32 i = 0; i < LINK_WIRELESS_QUEUE_SIZE; i++) {
|
||||
auto message = messages[i];
|
||||
if (message.packetId == LINK_WIRELESS_END)
|
||||
break;
|
||||
|
||||
#ifndef PROFILING_ENABLED
|
||||
u32 expected = counters[message.playerId] + 1;
|
||||
u32 expected = counters[message.playerId] + 1;
|
||||
#endif
|
||||
|
||||
counters[message.playerId] = message.data;
|
||||
counters[message.playerId] = message.data;
|
||||
|
||||
#ifndef PROFILING_ENABLED
|
||||
// Check for packet loss
|
||||
if (altView && message.data != expected) {
|
||||
lostPackets++;
|
||||
lastLostPacketPlayerId = message.playerId;
|
||||
lastLostPacketExpected = expected;
|
||||
lastLostPacketReceived = message.data;
|
||||
lastLostPacketReceivedPacketId = message.packetId;
|
||||
}
|
||||
#endif
|
||||
// Check for packet loss
|
||||
if (altView && message.data != expected) {
|
||||
lostPackets++;
|
||||
lastLostPacketPlayerId = message.playerId;
|
||||
lastLostPacketExpected = expected;
|
||||
lastLostPacketReceived = message.data;
|
||||
lastLostPacketReceivedPacketId = message.packetId;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// (8) Disconnect
|
||||
|
|
@ -348,8 +350,21 @@ void messageLoop() {
|
|||
}
|
||||
|
||||
// Packet loss check setting
|
||||
if (!switching && (keys & KEY_UP)) {
|
||||
switching = true;
|
||||
if (didPress(KEY_UP, switching)) {
|
||||
#ifdef PROFILING_ENABLED
|
||||
// In the profiler ROM, pressing UP will update the broadcast data
|
||||
if (linkWireless->getState() == LinkWireless::State::SERVING) {
|
||||
linkWireless->serve("LinkWireless",
|
||||
("N = " + std::to_string(counters[0])).c_str(),
|
||||
counters[0]);
|
||||
if (linkWireless->getLastError() ==
|
||||
LinkWireless::Error::BUSY_TRY_AGAIN) {
|
||||
log("Busy!");
|
||||
waitFor(KEY_DOWN);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
altView = !altView;
|
||||
#ifndef PROFILING_ENABLED
|
||||
if (!altView) {
|
||||
|
|
@ -360,8 +375,6 @@ void messageLoop() {
|
|||
}
|
||||
#endif
|
||||
}
|
||||
if (switching && (!(keys & KEY_UP)))
|
||||
switching = false;
|
||||
|
||||
// Normal output
|
||||
std::string output =
|
||||
|
|
@ -381,7 +394,12 @@ void messageLoop() {
|
|||
"p" + std::to_string(i) + ": " + std::to_string(counters[i]) + "\n";
|
||||
}
|
||||
|
||||
// Debug output
|
||||
// Debug output
|
||||
#ifdef LINK_WIRELESS_TWO_PLAYERS_ONLY
|
||||
output += "\n>> " + std::to_string(linkWireless->QUICK_SEND);
|
||||
output += "\n<< " + std::to_string(linkWireless->QUICK_RECEIVE) + "\n";
|
||||
#endif
|
||||
|
||||
output += "\n_buffer: " + std::to_string(linkWireless->_getPendingCount());
|
||||
if (retransmission && !altView) {
|
||||
output +=
|
||||
|
|
@ -405,24 +423,17 @@ void messageLoop() {
|
|||
output += "\n_onVBlank: " + std::to_string(linkWireless->lastVBlankTime);
|
||||
output += "\n_onSerial: " + std::to_string(linkWireless->lastSerialTime);
|
||||
output += "\n_onTimer: " + std::to_string(linkWireless->lastTimerTime);
|
||||
if (asyncACK)
|
||||
output += " | " + std::to_string(linkWireless->lastACKTimerTime);
|
||||
output +=
|
||||
"\n_serialIRQs: " + std::to_string(linkWireless->lastFrameSerialIRQs);
|
||||
output +=
|
||||
"\n_timerIRQs: " + std::to_string(linkWireless->lastFrameTimerIRQs);
|
||||
if (asyncACK)
|
||||
output += " | " + std::to_string(linkWireless->lastFrameACKTimerIRQs);
|
||||
output +=
|
||||
"\n_ms: " +
|
||||
std::to_string(linkWireless->toMs(
|
||||
linkWireless->lastVBlankTime +
|
||||
linkWireless->lastSerialTime * linkWireless->lastFrameSerialIRQs +
|
||||
linkWireless->lastTimerTime * linkWireless->lastFrameTimerIRQs +
|
||||
linkWireless->lastACKTimerTime *
|
||||
linkWireless->lastFrameACKTimerIRQs));
|
||||
#endif
|
||||
#ifndef PROFILING_ENABLED
|
||||
linkWireless->lastTimerTime * linkWireless->lastFrameTimerIRQs));
|
||||
#else
|
||||
if (lostPackets > 0) {
|
||||
output += "\n\n_lostPackets: " + std::to_string(lostPackets) + "\n";
|
||||
output += "_last: (" + std::to_string(lastLostPacketPlayerId) + ":" +
|
||||
|
|
@ -456,6 +467,18 @@ void waitFor(u16 key) {
|
|||
} while (!(keys & key));
|
||||
}
|
||||
|
||||
bool didPress(u16 key, bool& pressed) {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
bool isPressedNow = false;
|
||||
if ((keys & key) && !pressed) {
|
||||
pressed = true;
|
||||
isPressedNow = true;
|
||||
}
|
||||
if (pressed && !(keys & key))
|
||||
pressed = false;
|
||||
return isPressedNow;
|
||||
}
|
||||
|
||||
void wait(u32 verticalLines) {
|
||||
u32 count = 0;
|
||||
u32 vCount = REG_VCOUNT;
|
||||
|
|
@ -470,4 +493,4 @@ void wait(u32 verticalLines) {
|
|||
|
||||
void hang() {
|
||||
waitFor(KEY_DOWN);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include "interrupt.h"
|
||||
#include <ugba.h>
|
||||
#include <ugba/ugba.h>
|
||||
|
||||
void interrupt_init(void) {
|
||||
IRQ_Init();
|
||||
|
|
@ -19,4 +19,4 @@ void interrupt_disable(interrupt_index index) {
|
|||
|
||||
void interrupt_set_reference_vcount(unsigned long y) {
|
||||
IRQ_SetReferenceVCOUNT(y);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,4 +27,4 @@ void interrupt_enable(interrupt_index index);
|
|||
void interrupt_disable(interrupt_index index);
|
||||
void interrupt_set_reference_vcount(unsigned long y);
|
||||
|
||||
#endif // INTERRUPT_H
|
||||
#endif // INTERRUPT_H
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
# define EXPORT_API __declspec(dllexport)
|
||||
# else
|
||||
# define EXPORT_API __attribute__((visibility("default")))
|
||||
// Is this one below needed in MinGW?
|
||||
//# define EXPORT_API __attribute__((dllexport))
|
||||
# endif
|
||||
#endif
|
||||
|
|
@ -529,6 +529,14 @@ EXPORT_API uintptr_t *UGBA_RegDMA3DAD(void);
|
|||
// 3) When starting a timer by writing to:
|
||||
//
|
||||
// REG_TM0CNT_H, REG_TM1CNT_H, REG_TM2CNT_H, REG_TM3CNT_H
|
||||
//
|
||||
// 4) When IME or IE have been 0 for some time, IF isn't 0, and IME or IE are
|
||||
// set to a value that would trigger an interrupt:
|
||||
//
|
||||
// REG_IE, REG_IME
|
||||
//
|
||||
// Note that writing to IF doesn't work on the SDL2 port. On the GBA, writing
|
||||
// a 1 to a bit sets it to 0. On the SDL2 port, it sets the bit to 1.
|
||||
|
||||
#ifdef __GBA__
|
||||
# define UGBA_RegisterUpdatedOffset(offset) do { (void)(offset); } while (0)
|
||||