Compare commits

..

No commits in common. "master" and "v7.0.1" have entirely different histories.

169 changed files with 5039 additions and 12991 deletions

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Lorenzooone
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,7 +0,0 @@
Copyright 2024 Mattie Behrens.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,7 +0,0 @@
Arduino/Wiring Library for interfacing with a PS2 mouse.
Homepage
github.com/kristopher/PS2-Mouse-Arduino
License
MIT

View File

@ -1,44 +0,0 @@
Licenses for each *.bmp file in the graphics/ directory.
-----------------------------
start_3dhorse.bmp
-----------------------------
by Lu
CC BY-NC 4.0
---------------------------
explosion.bmp
---------------------------
by Afska
CC0
------------------------------------
common_fixed_8x16_font.bmp
common_fixed_8x16_font_accent.bmp
common_variable_8x16_font.bmp
common_variable_8x16_font_accent.bmp
------------------------------------
by Sparklin Labs by Pixel-boy: https://twitter.com/2pblog1
CC0
Their creation is funded by your support on Patreon (http://patreon.com/SparklinLabs) and through donations (http://sparklinlabs.itch.io/superpowers) Thanks!
Attribution/Licensing:
Creative Commons Zero: http://creativecommons.org/publicdomain/zero/1.0/
Attribution is not required but appreciated. Placing a link to http://superpowers-html5.com/ somewhere would be awesome :)
------------------------------------------
output_00001.bmp to output_00150.bmp
------------------------------------------
by RoyaltyFreeTube
CC BY 4.0
https://www.youtube.com/watch?v=1eHwlmn_Mps

View File

@ -1,11 +0,0 @@
Licenses for each *.pcm/*.gsm MUSIC file in the gbfs_files/ directory.
---------
cyberrid.mod
---------
by Jester
CC BY-NC-SA 4.0
https://modarchive.org/index.php?request=view_by_moduleid&query=40293

View File

@ -1,17 +0,0 @@
libagbabi 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.

View File

@ -1,19 +0,0 @@
zlib License
(C) 2020-2025 Gustavo Valiente (gustavo.valiente@prontonmail.com)
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.

View File

@ -1,21 +0,0 @@
The MIT License
Copyright (c) 2015 Manuel Sánchez
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,7 +0,0 @@
Copyright 2020 - 2021 DenSinH and fleroviux
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,5 +0,0 @@
https://raw.githubusercontent.com/devkitPro/devkitarm-crtls/master/gba_crt0.s
This Source Code Form is subject to the terms of the Mozilla Public License,
v. 2.0. If a copy of the MPL was not distributed with this file, You can
obtain one at https://mozilla.org/MPL/2.0/.

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 jwellbelove, https://github.com/ETLCPP/etl, http://www.etlcpp.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,30 +0,0 @@
MIT License
Copyright (c) 2020 Rodrigo Alfonso
Permission is hereby granted, free of charge, to any person obtaining a
copy
of this software and associated documentation files (the "Software"), to
deal
in the Software without restriction, including without limitation the
rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE
SOFTWARE.

View File

@ -1,7 +0,0 @@
Copyright 2019 João Baptista de Paula e Silva
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,19 +0,0 @@
Copyright (c) 2009-2022 Antonio Niño Díaz <antonio_nd@outlook.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,18 +0,0 @@
Copyright 2005-2009 J Vijn
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,23 +0,0 @@
/****************************************************************************
* __ *
* ____ ___ ____ __ ______ ___ ____ ____/ / *
* / __ `__ \/ __ `/ |/ / __ `__ \/ __ \/ __ / *
* / / / / / / /_/ /> </ / / / / / /_/ / /_/ / *
* /_/ /_/ /_/\__,_/_/|_/_/ /_/ /_/\____/\__,_/ *
* *
* Nintendo DS & Gameboy Advance Sound System *
* *
* Copyright (c) 2008, Mukunda Johnson (mukunda@maxmod.org) *
* *
* Permission to use, copy, modify, and/or distribute this software for any *
* purpose with or without fee is hereby granted, provided that the above *
* copyright notice and this permission notice appear in all copies. *
* *
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR *
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES *
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN *
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *
****************************************************************************/

View File

@ -1,7 +0,0 @@
The person or persons who have associated work with this document (the "Dedicator" or "Certifier") hereby either (a) certifies that, to the best of his knowledge, the work of authorship identified is in the public domain of the country from which the work is published, or (b) hereby dedicates whatever copyright the dedicators holds in the work of authorship identified below (the "Work") to the public domain. A certifier, moreover, dedicates any copyright interest he may have in the associated work, and for these purposes, is described as a "dedicator" below.
A certifier has taken reasonable steps to verify the copyright status of this work. Certifier recognizes that his good faith efforts may not shield him from liability if in fact the work certified is not in the public domain.
Dedicator makes this dedication for the benefit of the public at large and to the detriment of the Dedicator's heirs and successors. Dedicator intends this dedication to be an overt act of relinquishment in perpetuity of all present and future rights under copyright law, whether vested or contingent, in the Work. Dedicator understands that such relinquishment of all rights includes the relinquishment of all rights to enforce (by lawsuit or otherwise) those copyrights in the Work.
Dedicator recognizes that, once placed in the public domain, the Work may be freely reproduced, distributed, transmitted, used, modified, built upon, or otherwise exploited by anyone for any purpose, commercial or non-commercial, and in any way, including by methods that have not yet been invented or conceived.

View File

@ -1,19 +0,0 @@
Copyright (c) 2011-2015, 2019-2020 Antonio Niño Díaz <antonio_nd@outlook.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,23 +0,0 @@
/**
* Copyright (c) 2022 Luna Mittelbach
* Copyright (c) 2023 Adrian "asie" Siekierka
*
* 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.
*
* Originally from https://github.com/sdk-seven/runtime .
* Modified for the Wonderful toolchain.
*/

1
.gitignore vendored
View File

@ -5,7 +5,6 @@ node_modules/
# Files
examples/multiboot
loader.gbfs
.DS_Store
*.elf
*.gba

View File

@ -1,3 +0,0 @@
# ignore all files except *.md
*
!*.md

View File

@ -1,4 +0,0 @@
{
"tabWidth": 2,
"useTabs": false
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020-2025 Rodrigo Alfonso
Copyright (c) 2024 Rodrigo Alfonso
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

764
README.md

File diff suppressed because it is too large Load Diff

View File

@ -1,260 +0,0 @@
- 🌎 **Original file**: https://github.com/mattieb/4-e/blob/3332a11accb628a283b914ba1e003f5ea8dedbe3/NOTES.md 🌎
- ✏️ **Updates**: Added **Transfer** section.
# Notes
## Connection
The game and e-Reader (specifically, the game's custom e-Reader
dotcode scanner, which is separately sent by the game to the e-Reader
and saved there for future use) communicate over [SIO multiplayer
mode](https://problemkaputt.de/gbatek.htm#siomultiplayermode).
The game is player 0 and master; the e-Reader is player 1.
Baud rate is 115200.
As master, the game drives the serial clock. The e-Reader waits for
the game to be ready before sending any data. Having our program
wait for serial interrupts before sending data appears to work very
reliably.
## ✏️ Transfer
Here's how games transfer their _custom e-Reader dotcode scanner_ (aka _"DLC Loader"_):
### User
- Connects the 1P end of the Link Cable into the game
- Connects the 2P end of the Link Cable into the e-Reader
- On the e-Reader menu, goes to "Communication" -> "To Game Boy Advance" and press A
### Game
- Goes to MULTI mode and sends `0xFEFE`
- Sends `0xFEFE` again and expects `0xCCC0`
* On the Japanese e-Reader, every `0xCCC0` becomes `0xCCD0`
- Sends `0xCCC0` and expects `0xCCC0`
- Switches to NORMAL32, waits 1 frame, and sends the program length in bytes as a 32-bit number (hi part is probably always `0x0000`, since there's not enough flash memory in the e-Reader)
- Sends the loader bytes in 4-byte chunks in a _fire and forget_ way
- Sends `0x00000000`
- Sends a 32-bit checksum, which is the sum of all previous 32-bit values
- Switches to MULTI mode again, waits 1 frame
- Sends `0xCCC0` and expects `0xCCC0`
- Sends `0xCCC0` and expects `0x1`
- All sends need a small wait or otherwise it'll work on mGBA but not on hardware (waiting for two VCOUNT changes works, but it's probably just 36μs)
## Card scanning protocol
When the e-Reader detects that the connection has been established,
it starts the protocol:
1. The e-Reader sends FBFB until the game replies FBFB.
2. The e-Reader sends 5841 until the game replies 5841.
3. The e-Reader sends 4534 until the game replies 4534.
4. Next, the game takes control, sending one of the following:
- ECEC to request a demo card.
- EDED to request a power-up card.
- EEEE to request a level card.
The e-Reader replies F3F3. Both the game and the e-Reader
continue to send F3F3 and the request message while Lakitu flies
off the game screen.
5. The game sends F2F2 and the e-Reader replies F2F2. Both the
game and the e-Reader continue to send F2F2 while Lakitu flies onto
the e-Reader screen.
6. The e-Reader sends F1F1.
7. The game begins sending one of the following, expecting a scan to begin:
- EFEF when ready to receive a demo card.
- F0F0 when ready to receive a power-up card.
- FAFA when ready to receive a level card.
8. The e-Reader can cancel at this point by sending F7F7, if B is
pressed. It will then start [shutdown](#shutdown-protocol).
9. Once the e-Reader is ready to send, it sends F9F9. The game
begins sending FEFE repeatedly.
10. The e-Reader sends FDFD. The game stops sending, but is still
responsible for the serial clock.
11. The e-Reader sends the contents of the card, minus the 114-byte
header, in two-byte packets. They appear byteswapped in mGBA
logs (e.g. bytes "CE CF" in the decoded card appear as "CFCE")
but, in code, should be loaded as they appear (e.g. "0xcecf").
12. While transmitting, the e-Reader calculates a checksum by adding
each 16-bit packet value (e.g. "0xcecf" as in the last example)
to a 32-bit value that was initialized as zero. At the conclusion
of the card, this checksum is transmitted into two 16-bit
packets; least-significant first, most-significant second.
13. The e-Reader sends FCFC to end the card transmission.
14. If there was an transmission error (e.g. checksum failure), the
game sends F4F4. _(Next steps are currently undocumented.)_
15. The game sends F5F5 to acknowledge a successful transmission,
and the e-Reader begins [shutdown](#shutdown-protocol).
## Shutdown protocol
The e-Reader will do a clean shutdown of the communication session
in certain circumstances.
1. The e-Reader sends F2F2 while Lakitu flies off the screen.
2. The e-Reader sends F3F3 as its last packet, then stops transmitting.
3. The game then sends F1F1 as its last packet, then stops
transmitting.
At the conclusion of this protocol, the e-Reader is ready to restart
communications [from the beginning](#card-scanning-protocol) as if
it has just been started.
## Formats
### raw
.raw files are the complete binary data of the e-Reader card, with
error-correction applied, which can be rendered as dotcodes.
[nedcenc](https://github.com/Lymia/nedclib) can translate between
.raw and [.bin](#bin) files.
### bin
.bin files are the complete binary data of the e-Reader card, without
error-correction applied. The e-Reader will decode cards to this
internally.
| Offset | Size | Value |
| ------ | ---- | ----- |
| 0 | 114 | Card header |
| 114 | 1998 | [Card contents](#card-contents) |
The e-Reader requires the card header to be intact and correct. 4-e
will ignore the card header entirely, seeking 114 bytes into the
.bin file.
### sav
A Super Mario Advance 4 Flash save can "save" up to 32 levels, and
an additional in-progress level that, when beaten, moves to one of
the saved level slots.
| Offset | Size | Value |
| ------ | ---- | ----- |
| 800 | 1998 | In-progress [level contents](#level-contents) |
| 24592 | 1998 | Saved level 1 contents |
| 26624 | 1998 | Saved level 2 contents |
| 28688 | 1998 | Saved level 3 contents |
| 30720 | 1998 | Saved level 4 contents |
| 32784 | 1998 | Saved level 5 contents |
| 34816 | 1998 | Saved level 6 contents |
| 36880 | 1998 | Saved level 7 contents |
| 38912 | 1998 | Saved level 8 contents |
| 40976 | 1998 | Saved level 9 contents |
| 43008 | 1998 | Saved level 10 contents |
| 45072 | 1998 | Saved level 11 contents |
| 47104 | 1998 | Saved level 12 contents |
| 49168 | 1998 | Saved level 13 contents |
| 51200 | 1998 | Saved level 14 contents |
| 53264 | 1998 | Saved level 15 contents |
| 55296 | 1998 | Saved level 16 contents |
| 57360 | 1998 | Saved level 17 contents |
| 59392 | 1998 | Saved level 18 contents |
| 61456 | 1998 | Saved level 19 contents |
| 63488 | 1998 | Saved level 20 contents |
| 67584 | 1998 | In-progress level contents backup copy |
| 90128 | 1998 | Saved level 21 contents |
| 92160 | 1998 | Saved level 22 contents |
| 94224 | 1998 | Saved level 23 contents |
| 96256 | 1998 | Saved level 24 contents |
| 98320 | 1998 | Saved level 25 contents |
| 100352 | 1998 | Saved level 26 contents |
| 102416 | 1998 | Saved level 27 contents |
| 104448 | 1998 | Saved level 28 contents |
| 106512 | 1998 | Saved level 29 contents |
| 108544 | 1998 | Saved level 30 contents |
| 110608 | 1998 | Saved level 31 contents |
| 112640 | 1998 | Saved level 32 contents |
The saved level offsets seem a bit arbitrary but, in fact, follow
a pattern:
- For slots 1 to 19, start at offset 24576, and for each slot,
move 2048 bytes forward. (24576, 26624, 28672, 30702, etc.)
- For slots 20 to 32, start at offset 90112, and for each slot,
move 2048 bytes forward. (90112, 92160, 94208, 96256, etc.)
- For odd-numbered slots (1, 3, etc.), the contents start an
additional 16 bytes in. (24592, 28688, etc.)
### Card contents
Card contents are transmitted in their entirety during the contents
step of the [card scanning protocol](#card-scanning-protocol).
They are always 1998 bytes long.
#### Level contents
When the [card contents](#card-contents) is a level:
| Offset | Size | Value |
| ------ | ---- | ----- |
| 0 | 1 | Level id |
| 1 | 7 | Bytes: 00 ff ff ff ff ff 00
| 8 | 1990 | [lcmp](#lcmp) |
The level id matches the last part of the [e-Card
number](https://www.mariowiki.com/Super_Mario_Advance_4:_Super_Mario_Bros._3_e-Reader_cards);
for example, Classic World 1-1 is numbered 07-A001 and has level
id is 1, and Airship's Revenge is numbered 07-A051 and has level
id is 51.
Official level ids 1-5 are "Classic" levels, 11-40 standard levels,
and 50-53 promotional levels. Re-using official level ids may cause
these levels to be overwritten.
While it's apparently technically possible to use any level id, at
least one
[report](https://discord.com/channels/884534133496365157/884534133496365160/1223234994907119667)
on the Smaghetti Discord suggests level ids higher than 72 (decimal)
will "mess up the level list".
### lcmp
.lcmp files are the portion of a level card that contains the
compressed level data itself, compressed using what is known as
"ASR0" compression.
| Offset | Size | Value |
| ------ | ---- | ----- |
| 0 | 4 | `"ASR0"` |
| 4 | 1986 | Compressed level data |
.lcmp files can be converted to [level contents](#level-contents),
given a level id.
.lcmp files can be decompressed into [.level](#level) files and
compressed again with sma4comp.
### level
.level files are the uncompressed level data.

View File

@ -1,59 +0,0 @@
digraph finite_state_machine {
node [shape=circle, fixedsize=true, width=0.75, fontname="Courier New", style=filled, fillcolor="lightgray", fontcolor="black"];
LinkCable [label="👾"];
LinkCableMultiboot [label="💻"]
LinkRawCable [label="🔧👾"]
LinkWireless [label="📻"]
LinkWirelessMultiboot [label="📡"]
LinkRawWireless [label="🔧📻"]
LinkWirelessOpenSDK [label="🔧🏛"]
LinkUniversal [label="🌎"]
LinkGPIO [label="🔌"]
LinkSPI [label="🔗"]
LinkUART [label="⏱"]
LinkCube [label="🟪"]
LinkCard [label="💳"]
LinkMobile [label="📱"]
LinkIR [label="📺"]
LinkPS2Mouse [label="🖱️"]
LinkPS2Keyboard [label="⌨️"]
//Common [label="⚙️"]
edge [fontname="Courier New", fontcolor="black"];
/*LinkCable -> Common
LinkRawCable -> Common
LinkWireless -> Common
LinkCableMultiboot -> Common
LinkWirelessMultiboot -> Common
LinkRawWireless -> Common
LinkWirelessOpenSDK -> Common
LinkUniversal -> Common
LinkGPIO -> Common
LinkSPI -> Common
LinkUART -> Common
LinkCube -> Common
LinkCard -> Common
LinkMobile -> Common
LinkIR -> Common
LinkPS2Mouse -> Common
LinkPS2Keyboard -> Common*/
LinkCable -> LinkRawCable
LinkCableMultiboot -> LinkRawCable
LinkCableMultiboot -> LinkSPI
LinkWireless -> LinkRawWireless
LinkWirelessMultiboot -> LinkRawWireless
LinkWirelessMultiboot -> LinkWirelessOpenSDK
LinkRawWireless -> LinkGPIO
LinkRawWireless -> LinkSPI
LinkWirelessOpenSDK -> LinkRawWireless
LinkUniversal -> LinkCable
LinkUniversal -> LinkWireless
LinkCard -> LinkRawCable
LinkCard -> LinkSPI
LinkMobile -> LinkGPIO
LinkMobile -> LinkSPI
LinkIR -> LinkGPIO
}

View File

@ -1,204 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="511pt" height="332pt" viewBox="0.00 0.00 511.00 332.00">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 328)">
<title>finite_state_machine</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-328 507,-328 507,4 -4,4"/>
<!-- LinkCable -->
<g id="node1" class="node">
<title>LinkCable</title>
<ellipse fill="lightgray" stroke="black" cx="27" cy="-207" rx="27" ry="27"/>
<text text-anchor="middle" x="27" y="-202.8" font-family="Courier New" font-size="14.00">👾</text>
</g>
<!-- LinkRawCable -->
<g id="node3" class="node">
<title>LinkRawCable</title>
<ellipse fill="lightgray" stroke="black" cx="50" cy="-27" rx="27" ry="27"/>
<text text-anchor="middle" x="50" y="-22.8" font-family="Courier New" font-size="14.00">🔧👾</text>
</g>
<!-- LinkCable&#45;&gt;LinkRawCable -->
<g id="edge1" class="edge">
<title>LinkCable-&gt;LinkRawCable</title>
<path fill="none" stroke="black" d="M18.96,-181.16C12.49,-157.39 5.73,-120.72 14,-90 16.93,-79.12 22.29,-68.18 28,-58.58"/>
<polygon fill="black" stroke="black" points="30.78,-60.73 33.17,-50.4 24.87,-56.98 30.78,-60.73"/>
</g>
<!-- LinkCableMultiboot -->
<g id="node2" class="node">
<title>LinkCableMultiboot</title>
<ellipse fill="lightgray" stroke="black" cx="50" cy="-117" rx="27" ry="27"/>
<text text-anchor="middle" x="50" y="-112.8" font-family="Courier New" font-size="14.00">💻</text>
</g>
<!-- LinkCableMultiboot&#45;&gt;LinkRawCable -->
<g id="edge2" class="edge">
<title>LinkCableMultiboot-&gt;LinkRawCable</title>
<path fill="none" stroke="black" d="M50,-89.6C50,-82.11 50,-73.82 50,-65.8"/>
<polygon fill="black" stroke="black" points="53.5,-65.9 50,-55.9 46.5,-65.9 53.5,-65.9"/>
</g>
<!-- LinkSPI -->
<g id="node10" class="node">
<title>LinkSPI</title>
<ellipse fill="lightgray" stroke="black" cx="158" cy="-27" rx="27" ry="27"/>
<text text-anchor="middle" x="158" y="-22.8" font-family="Courier New" font-size="14.00">🔗</text>
</g>
<!-- LinkCableMultiboot&#45;&gt;LinkSPI -->
<g id="edge3" class="edge">
<title>LinkCableMultiboot-&gt;LinkSPI</title>
<path fill="none" stroke="black" d="M70.57,-99.24C87.01,-85.85 110.26,-66.9 128.66,-51.91"/>
<polygon fill="black" stroke="black" points="130.79,-54.68 136.34,-45.65 126.37,-49.26 130.79,-54.68"/>
</g>
<!-- LinkWireless -->
<g id="node4" class="node">
<title>LinkWireless</title>
<ellipse fill="lightgray" stroke="black" cx="107" cy="-207" rx="27" ry="27"/>
<text text-anchor="middle" x="107" y="-202.8" font-family="Courier New" font-size="14.00">📻</text>
</g>
<!-- LinkRawWireless -->
<g id="node6" class="node">
<title>LinkRawWireless</title>
<ellipse fill="lightgray" stroke="black" cx="194" cy="-117" rx="27" ry="27"/>
<text text-anchor="middle" x="194" y="-112.8" font-family="Courier New" font-size="14.00">🔧📻</text>
</g>
<!-- LinkWireless&#45;&gt;LinkRawWireless -->
<g id="edge4" class="edge">
<title>LinkWireless-&gt;LinkRawWireless</title>
<path fill="none" stroke="black" d="M125.45,-187.34C137.59,-175.05 153.69,-158.77 167.23,-145.08"/>
<polygon fill="black" stroke="black" points="169.67,-147.59 174.21,-138.02 164.69,-142.67 169.67,-147.59"/>
</g>
<!-- LinkWirelessMultiboot -->
<g id="node5" class="node">
<title>LinkWirelessMultiboot</title>
<ellipse fill="lightgray" stroke="black" cx="188" cy="-297" rx="27" ry="27"/>
<text text-anchor="middle" x="188" y="-292.8" font-family="Courier New" font-size="14.00">📡</text>
</g>
<!-- LinkWirelessMultiboot&#45;&gt;LinkRawWireless -->
<g id="edge5" class="edge">
<title>LinkWirelessMultiboot-&gt;LinkRawWireless</title>
<path fill="none" stroke="black" d="M185.86,-269.71C184.29,-246.17 182.78,-210.74 185,-180 185.59,-171.89 186.6,-163.23 187.74,-155.03"/>
<polygon fill="black" stroke="black" points="191.17,-155.76 189.18,-145.35 184.24,-154.73 191.17,-155.76"/>
</g>
<!-- LinkWirelessOpenSDK -->
<g id="node7" class="node">
<title>LinkWirelessOpenSDK</title>
<ellipse fill="lightgray" stroke="black" cx="221" cy="-207" rx="27" ry="27"/>
<text text-anchor="middle" x="221" y="-202.8" font-family="Courier New" font-size="14.00">🔧🏛</text>
</g>
<!-- LinkWirelessMultiboot&#45;&gt;LinkWirelessOpenSDK -->
<g id="edge6" class="edge">
<title>LinkWirelessMultiboot-&gt;LinkWirelessOpenSDK</title>
<path fill="none" stroke="black" d="M197.19,-271.49C200.45,-262.79 204.19,-252.81 207.73,-243.39"/>
<polygon fill="black" stroke="black" points="210.98,-244.7 211.21,-234.1 204.42,-242.24 210.98,-244.7"/>
</g>
<!-- LinkGPIO -->
<g id="node9" class="node">
<title>LinkGPIO</title>
<ellipse fill="lightgray" stroke="black" cx="266" cy="-27" rx="27" ry="27"/>
<text text-anchor="middle" x="266" y="-22.8" font-family="Courier New" font-size="14.00">🔌</text>
</g>
<!-- LinkRawWireless&#45;&gt;LinkGPIO -->
<g id="edge7" class="edge">
<title>LinkRawWireless-&gt;LinkGPIO</title>
<path fill="none" stroke="black" d="M210.7,-95.59C219.99,-84.23 231.72,-69.9 241.98,-57.35"/>
<polygon fill="black" stroke="black" points="244.56,-59.73 248.18,-49.78 239.14,-55.3 244.56,-59.73"/>
</g>
<!-- LinkRawWireless&#45;&gt;LinkSPI -->
<g id="edge8" class="edge">
<title>LinkRawWireless-&gt;LinkSPI</title>
<path fill="none" stroke="black" d="M183.97,-91.49C180.34,-82.62 176.17,-72.43 172.25,-62.84"/>
<polygon fill="black" stroke="black" points="175.57,-61.71 168.55,-53.78 169.09,-64.36 175.57,-61.71"/>
</g>
<!-- LinkWirelessOpenSDK&#45;&gt;LinkRawWireless -->
<g id="edge9" class="edge">
<title>LinkWirelessOpenSDK-&gt;LinkRawWireless</title>
<path fill="none" stroke="black" d="M213.34,-181.02C210.76,-172.64 207.84,-163.12 205.06,-154.06"/>
<polygon fill="black" stroke="black" points="208.5,-153.33 202.22,-144.8 201.81,-155.38 208.5,-153.33"/>
</g>
<!-- LinkUniversal -->
<g id="node8" class="node">
<title>LinkUniversal</title>
<ellipse fill="lightgray" stroke="black" cx="89" cy="-297" rx="27" ry="27"/>
<text text-anchor="middle" x="89" y="-292.8" font-family="Courier New" font-size="14.00">🌎</text>
</g>
<!-- LinkUniversal&#45;&gt;LinkCable -->
<g id="edge10" class="edge">
<title>LinkUniversal-&gt;LinkCable</title>
<path fill="none" stroke="black" d="M73.67,-274.25C66.22,-263.67 57.13,-250.77 48.96,-239.17"/>
<polygon fill="black" stroke="black" points="51.86,-237.2 43.24,-231.04 46.13,-241.24 51.86,-237.2"/>
</g>
<!-- LinkUniversal&#45;&gt;LinkWireless -->
<g id="edge11" class="edge">
<title>LinkUniversal-&gt;LinkWireless</title>
<path fill="none" stroke="black" d="M94.3,-270.07C95.91,-262.2 97.72,-253.38 99.45,-244.93"/>
<polygon fill="black" stroke="black" points="102.86,-245.72 101.43,-235.22 96,-244.32 102.86,-245.72"/>
</g>
<!-- LinkUART -->
<g id="node11" class="node">
<title>LinkUART</title>
<ellipse fill="lightgray" stroke="black" cx="260" cy="-297" rx="27" ry="27"/>
<text text-anchor="middle" x="260" y="-292.8" font-family="Courier New" font-size="14.00"></text>
</g>
<!-- LinkCube -->
<g id="node12" class="node">
<title>LinkCube</title>
<ellipse fill="lightgray" stroke="black" cx="332" cy="-297" rx="27" ry="27"/>
<text text-anchor="middle" x="332" y="-292.8" font-family="Courier New" font-size="14.00">🟪</text>
</g>
<!-- LinkCard -->
<g id="node13" class="node">
<title>LinkCard</title>
<ellipse fill="lightgray" stroke="black" cx="122" cy="-117" rx="27" ry="27"/>
<text text-anchor="middle" x="122" y="-112.8" font-family="Courier New" font-size="14.00">💳</text>
</g>
<!-- LinkCard&#45;&gt;LinkRawCable -->
<g id="edge12" class="edge">
<title>LinkCard-&gt;LinkRawCable</title>
<path fill="none" stroke="black" d="M105.3,-95.59C96.01,-84.23 84.28,-69.9 74.02,-57.35"/>
<polygon fill="black" stroke="black" points="76.86,-55.3 67.82,-49.78 71.44,-59.73 76.86,-55.3"/>
</g>
<!-- LinkCard&#45;&gt;LinkSPI -->
<g id="edge13" class="edge">
<title>LinkCard-&gt;LinkSPI</title>
<path fill="none" stroke="black" d="M132.03,-91.49C135.66,-82.62 139.83,-72.43 143.75,-62.84"/>
<polygon fill="black" stroke="black" points="146.91,-64.36 147.45,-53.78 140.43,-61.71 146.91,-64.36"/>
</g>
<!-- LinkMobile -->
<g id="node14" class="node">
<title>LinkMobile</title>
<ellipse fill="lightgray" stroke="black" cx="266" cy="-117" rx="27" ry="27"/>
<text text-anchor="middle" x="266" y="-112.8" font-family="Courier New" font-size="14.00">📱</text>
</g>
<!-- LinkMobile&#45;&gt;LinkGPIO -->
<g id="edge14" class="edge">
<title>LinkMobile-&gt;LinkGPIO</title>
<path fill="none" stroke="black" d="M266,-89.6C266,-82.11 266,-73.82 266,-65.8"/>
<polygon fill="black" stroke="black" points="269.5,-65.9 266,-55.9 262.5,-65.9 269.5,-65.9"/>
</g>
<!-- LinkMobile&#45;&gt;LinkSPI -->
<g id="edge15" class="edge">
<title>LinkMobile-&gt;LinkSPI</title>
<path fill="none" stroke="black" d="M245.43,-99.24C228.99,-85.85 205.74,-66.9 187.34,-51.91"/>
<polygon fill="black" stroke="black" points="189.63,-49.26 179.66,-45.65 185.21,-54.68 189.63,-49.26"/>
</g>
<!-- LinkIR -->
<g id="node15" class="node">
<title>LinkIR</title>
<ellipse fill="lightgray" stroke="black" cx="338" cy="-117" rx="27" ry="27"/>
<text text-anchor="middle" x="338" y="-112.8" font-family="Courier New" font-size="14.00">📺</text>
</g>
<!-- LinkIR&#45;&gt;LinkGPIO -->
<g id="edge16" class="edge">
<title>LinkIR-&gt;LinkGPIO</title>
<path fill="none" stroke="black" d="M321.3,-95.59C312.01,-84.23 300.28,-69.9 290.02,-57.35"/>
<polygon fill="black" stroke="black" points="292.86,-55.3 283.82,-49.78 287.44,-59.73 292.86,-55.3"/>
</g>
<!-- LinkPS2Mouse -->
<g id="node16" class="node">
<title>LinkPS2Mouse</title>
<ellipse fill="lightgray" stroke="black" cx="404" cy="-297" rx="27" ry="27"/>
<text text-anchor="middle" x="404" y="-292.8" font-family="Courier New" font-size="14.00">🖱️</text>
</g>
<!-- LinkPS2Keyboard -->
<g id="node17" class="node">
<title>LinkPS2Keyboard</title>
<ellipse fill="lightgray" stroke="black" cx="476" cy="-297" rx="27" ry="27"/>
<text text-anchor="middle" x="476" y="-292.8" font-family="Courier New" font-size="14.00">⌨️</text>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 KiB

After

Width:  |  Height:  |  Size: 812 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1023 KiB

After

Width:  |  Height:  |  Size: 906 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

View File

@ -1,172 +0,0 @@
🌎 From: https://shonumi.github.io/dandocs.html#agb006 🌎
# AGB-006
- [General Hardware Information](#gbair_gen)
- [Device Detection](#gbair_dev)
- [IR Operation](#gbair_opr)
- [Comparison to GBC IR](#gbair_cmp)
- [Zoid Commands](#gbair_cmd)
- [Prototype IR Port](#gbair_pro)
## \[AGB-006\] : General Hardware Information
The AGB-006 is an accessory released in conjunction with Cyber Drive Zoids: Kiju no Senshi Hyuu on July 18, 2003. It serves as an infrared adapter, coming as a bundle with each game. Using IR signals, players can turn their GBAs into remote controls to pilot three toy model Zoids. Although the GBA removed the GBC's IR port, the AGB-006 officially restored that functionality as an add-on. Unfortunately Cyber Drive Zoids was the only game to take adavantage of the AGB-006.
- The AGB-006 is a small attachment that fits into the GBA serial port providing IR functionalities
- Has 2 IR diodes, one for receiving and one for transmitting
- Very similar in size and shape to the connecting end of a DOL-011 (Gamecube-to-GBA cable)
- Compatible with CDZ-01 Diablotiger, CDZ-02 Cyclops, and CDZ-EX Diablotiger B
## \[AGB-006\] : Device Detection
The AGB-006 can be detected in software by generating an interrupt request in General Purpose mode whenever sending an OFF/ON pulse. When no AGB-006 is plugged in, no interrupts are triggered, so checking Bit 7 of the Interrupt Request Flags is sufficient to verify whether or not the adapter is present. At a minimum, two writes are necessary. The first sets the SO line LOW (turning the IR signal off). The second sets the SO line HIGH (turning the IR signal on) and enables interrupts. The following RCNT values are sent by Cyber Drive Zoids to trigger an interrupt request:
```
0x80B2 //Turn IR light off
0x81BA //Turn IR light on and enable Serial I/O interrupts
```
For this example, after the interrupt is generated, RCNT should hold the value 0x2004 at that time.
## \[AGB-006\] : IR Operation
The following values of RCNT can be used for various IR-related functions:
```
----------------------------------------
Value | Usage
----------------------------------------
0x80BA | Turns IR signal on
0x80B2 | Turns IR signal off
0x8190 | Receive IR signal
```
For turning the IR signal on, set the SO line HIGH. For turning the IR light off, set the SO line LOW.
For receiving IR signals, set SI's direction as input. An interrupt will be requested once the signal arrives. This interrupt is only generated when the IR sensor begins detecting light when it previously detected none. Essentially it comes when transitioning from a "no light" to "light" state. There doesn't appear to be a way to get an interrupt once the signal goes off. It should be noted that Cyberdrive Zoids, the only officially supported game for the AGB-006, is not programmed to receive IR signals. This feature was thus never used in commercial software.
Both sending and receiving signals can trigger an interrupt if Bit 8 of RCNT is set. For receiving IR signals, interrupts are the most effective method of detecting an incoming signal, as simply waiting for RCNT to change its read value is unreliable (unlike the device detection phase). Writing the value 0x8190 to RCNT, for example, instantly changes its read value to 0x8196, even when no AGB-006 is connected. For this reason, interrupts are all but required for properly receiving IR signals. Requesting interrupts while sending IR signals is optional but of limited use.
When receiving an IR signal, software should in theory measure the delay until the next IR signal to get the total ON/OFF pulse duration. Once RCNT is set, IR interrupts are continuously generated whenever a new IR signal is detected, as long as Bit 7 of the Interrupt Request Flags is cleared after each pulse. The so-called "signal fade" present in GBC IR hardware is not evident in the AGB-006, so the accessory will not automatically stop receiving an IR signal after a set amount of time. Software should set a cut-off threshold for pulse durations to decide when one side has stopped sending, especially as the AGB-006 does not have any apparent feedback to manually check.
## \[AGB-006\] : Comparison to GBC IR
The biggest difference between the AGB-006 and the GBC's native IR hardware are interrupts. While prototype GBAs originally were designed to have IR ports with their own associated interrupts, the AGB-006 repurposes the existing serial interrupt. Interrupts potentially simplify the task of detecting IR light. Depending on the communication protocol, however, actually servicing an interrupt may be too slow for rapid data transfer. In such cases, a tight loop that merely continuously reads the Interrupt Request Flags may be more effecient. The AGB-006's interrupts excel in convenience over the GBC when detecting the next IR signal, as they eliminate the need to check any OFF status of the IR signal. The only thing a program needs to do is maintain a maximum allowed duration for pulses to see when communications should stop (something GBC software does anyway to check for errors mid-transmission).
Physically speaking, the AGB-006 has a far wider range of reception. It is capable of receiving signals at a maximum distance roughly between 1.7 and 1.8 meters. Furthermore, it can receive signals approximately within 150 degrees horizontally, and approximately 165 degrees vertically (IR light is only undetectable from very extreme angles beneath the unit). This stands in contrast to the GBC, which mostly needs line-of-sight to communicate via IR, or even detect IR sources.
Finally, while the GBC IR hardware is receptive to incandescent light sources, the AGB-006 largely ignores them.
## \[AGB-006\] : Zoid Commands
Officially, the AGB-006 is exclusively used to control motorized minature models from the Zoids franchise. This communication is always one-way, with the GBA blasting IR signals repeatedly once a button is pressed. The protocol is fairly simple. A number of IR pulses (total ON and OFF time) are sent with only a few specific lengths. Their exact timing seems to vary and may not be entirely consistent, so relative duration must be used. The ON phase of the pulse accounts for a very miniscule amount time, while the OFF phase accounts for the majority of the delay.
```
Mini Pulse Smallest pulse. Very short and very numerous
Short Pulse Roughly 16x the length of a Mini Pulse
Medium Pulse Roughly 40x the length of a Mini Pulse
Long Pulse The longest pulse. Easily around 6000x the length of a Mini Pulse
```
Mini Pulses appear to last anywhere from 18.1us to 20us. There are dozens of them in between every other type of pulse, and they seem to act as a sort of "keep-alive" for IR transmission. Below are the specific transfers used to manipulate the Zoids. There are two separate "channels" for ID1 and ID2. The goal was to have two players compete against each other, so two distinct versions of each command exist to avoid interference.
When omitting Mini Pulses, each IR command is 12 Short or Medium Pulses followed by 1 Long Pulse. The order of Short and Medium Pulses determines the exact command, while the Long Pulse functions as a stop signal. Each command can be converted into binary by treating a Medium Pulse as "1", and a Short Pulse as a "0". Commands can then be parsed by examining every 4-bit segment. Below describes how each segment affects the command. Note that the values use the first pulse as the MSB and the last pulse as the LSB.
```
-----------------------------------------------------------
Bits 8-11 - Channel + Action Category
-----------------------------------------------------------
0x8 Perform "Main Actions" for ID1
0x9 Perform "Misc. Actions" for ID1
0xA Perform "Main Actions" for ID2
0xB Perform "Misc. Actions" for ID2
-----------------------------------------------------------
```
Bits 8-11 determine which channel the command belongs to, as well as the overall category of action the CDZ model should perform. Main Actions describe things such as moving forward/backward, firing the cannon, and turning. Misc. Actions describe a variety of things such as syncing/initializing the GBA and CDZ model, roaring, and retreating. It is also used for some actions during the "boost" mode. A number of these actions are not listed in the game's manual.
```
-----------------------------------------------------------
Bits 4-7 - Motion Type
-----------------------------------------------------------
For Main Actions
0x2 Boost Backward
0x3 Boost Backward
0x4 Backward Speed 2
0x6 Backward Speed 1
0x8 Backward Speed 0
0xA Forward Speed 0
0xC Forward Speed 1
0xE Forward Speed 2
For Misc. Actions
0x0 Boost Forward
0x1 Boost Forward + Fire (optionally)
0x3 Sync Signal/Hidden Moves
0x5 Hidden Moves
-----------------------------------------------------------
```
The Cyber Drive Zoids game can use the save data from the single-player mode to enhance the actions performed by the CDZ model, chiefly in regards to speed, HP, and the ability to boost. Once sufficient progress is made in the single-player mode, the CDZ model can shift gears up and down to alter speed. The regular IR controller that comes packaged by default with CDZ models does not offer these advantages. The sync signal is used on startup to activate the CDZ model for a match.
```
-----------------------------------------------------------
Bits 0-3 - Action Type
-----------------------------------------------------------
For Main Actions
0x0 Walk OR Fire if Motion Type == 1 or 3
0x1 Walk OR Fire if Motion Type == 1 or 3
0x2 Jump (typically for Boost Mode)
0x3 Jump (typically for Boost Mode)
0x4 Jump
0x5 Jump
0x6 Jump
0x7 Jump
0x8 Jump
0x9 Jump
0xA Fire
0xB Fire
0xC Fire
0xD Fire
For Misc. Actions
0x0 If Motion Type == 1 -> Fire
0x1 If Motion Type == 1 -> Fire
0x2 If Motion Type == 0 -> Jump
If Motion Type == 3 -> Sync ID1, Boost Level 0
If Motion Type == 5 -> Intimidate
0x3 If Motion Type == 0 -> Jump
If Motion Type == 3 -> Sync ID2, Boost Level 0
If Motion Type == 5 -> Intimidate
0x4 If Motion Type == 3 -> Sync ID1, Boost Level 1
If Motion Type == 5 -> Swing Hips
0x5 If Motion Type == 3 -> Sync ID2, Boost Level 1
If Motion Type == 5 -> Swing Hips
0xA War Cry
0xB War Cry
0xE Escape
0xF Escape
-----------------------------------------------------------
```
For Main Actions, if the Motion Type is not one of the above specified values (e.g. a 0), and the Action Type is jumping or firing, then the Zoid will move not forward or backward. Instead, it will remain stationary while performing that action. In this same manner, it's possible to simultaneously combine jumping or firing with moving by properly setting the Motion Type. Walking should always specify one of the listed Motion Types, however.
The CDZ model has a boost mode where the toy is temporarily invulnerable to damage and shifts into the highest gear. This mode lasts for 10 seconds. The sync signals specify how many boosts are available per match for that CDZ model. The boost mode commands are spread across Main Actions and Misc. Operations such as firing and jumping are often conditional, requiring the Motion Type to be Boost Forward or Boost Backward.
## \[AGB-006\] : Prototype IR Port
Cyber Drive Zoids appears to frequently read from the memory location 0x4000136 which was supposed to have been the MMIO register for the IR port on prototype GBAs. The AGB-006 does not cause the 16-bit value of that location to change at all, however, and all reads do in fact return zero. The purpose of that bit of code is currently unclear. It may be a remnant of earlier code long made before the AGB-006 was fully realized.

View File

@ -13,15 +13,15 @@
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
* 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.
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.
@ -32,31 +32,31 @@ 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 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
* 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.
@ -68,7 +68,7 @@ On the GBC, the Mobile Adapter operates using the fastest available setting (64K
```
-------------------------------------------------
Section | Length
Section | Length
-------------------------------------------------
Magic Bytes : 0x99 0x66 | 2 bytes
Packet Header | 4 bytes
@ -96,7 +96,7 @@ Bytes 0-254 | Arbitrary data
-------------------------------------------------
Packet Checksum
Packet Checksum
-------------------------------------------------
Byte 1 | High byte of 16-bit sum
Byte 2 | Low byte of 16-bit sum
@ -144,15 +144,14 @@ Even though the protocol effectively enables 2-way communication between the Gam
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 |
---
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.
@ -344,7 +343,7 @@ 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.
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**

View File

@ -23,7 +23,7 @@ _You can make this screen display any game_
When I started, I used the following resources to start being able to talk with the wireless adapter:
- [This Gist contains some details](https://gist.github.com/iracigt/50b3a857e4d82c2c11d0dd5f84ecac6b)
- [This Gist contains some details](wireless.txt)
- [GBATEK has a section on the wireless adapter](gbatek.md)
## Pinout
@ -237,13 +237,13 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
[![Image without alt text or caption](img/wireless/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 broadcasting game name and username in download play, the union room, and the username in direct multiplayer in Pokémon.
- 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 values conventionally contain bytes indicating the game id and whether the server should appear in the Download Play list or not. When in download play mode, the remaining data is the ASCII characters of the game and user name. When used for in-game multiplayer (as in the union room) both the game name and user name bytes can have arbitrary meaning or encoding. In any case, the content of the broadcast data is not checked or validated by the adapter hardware. Here's a byte by byte explanation of download play mode:
💻 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:
[![Image without alt text or caption](img/wireless/broadcast.png)](img/wireless/broadcast.png)
If you read from right to left, it says `ICE CLIMBER` as the game name and `NINTENDO` as the user name. Note that the byte marked `<sep>` is a checksum used in both download play and direct play modes, but like the rest of the format it is not enforced at the hardware level. For how the checksum is calculated, see this example code from [Pokémon FireRed](https://github.com/pret/pokefirered/blob/4f5fe2a27941770cb1d7c33fcc1fd4c9495838af/src/librfu_rfu.c#L680).
(if you read from right to left, it says `ICE CLIMBER` - `NINTENDO`)
🆔 The **Game ID** is what games use to avoid listing servers from another game. This is done on the software layer (GBA), the adapter does not enforce this in any way, nor does gba-link-connection (unless `LINK_UNIVERSAL_GAME_ID_FILTER` is set).
@ -254,27 +254,20 @@ If you read from right to left, it says `ICE CLIMBER` as the game name and `NINT
- Send length: 0, response length: 0
- This uses the broadcast data given by the broadcast command and actually does the broadcasting.
⏲ After calling this command, wait some time (~15 scanlines) before calling `PollConnections` or it will fail!.
#### EndHost - `0x1b`
- Send length: 0, response length: 2+
- This command stops host broadcast. This allows to "close" the room and stop allowing new clients, but also **keeping the existing connections alive**. Sends and Receives still work, but:
- 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 `PollConnections` on the host side will fail, unless `StartHost` is called again.
- Calls to `AcceptConnections` on the host side will fail, unless `StartHost` is called again.
#### BroadcastRead - `0x1c`, `0x1d` and `0x1e`
[![Image without alt text or caption](img/wireless/0x1d.png)](img/wireless/0x1d.png)
Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEnd`.
- Send length: 0
- Response length:
- `0x1c`: 0
- `0x1d` and `0x1e`: and 7 \* number of broadcasts (maximum: 4)
- 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 word contains:
- 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.
@ -288,24 +281,20 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
🆔 IDs are randomly generated. Each time you broadcast or connect, the adapter assigns you a new ID.
✅ Reading broadcasts is a three-step process: First, you send `0x1c` (you will get an ACK instantly and no data) to put the adapter in 'broadcast reading' mode, and start waiting until the adapter retrieves data (games usually wait 1 full second). Then, send a `0x1d` and it will return what's described above. Lastly, send a `0x1e` to finish the process (you can ignore what the adapter returns here), which exits broadcast reading mode. If you don't send that last `0x1e`, the next command will fail.
✅ Reading broadcasts is a three-step process: First, you send `0x1c` (you will get an ACK instantly), and 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.
⌚ Although games wait 1 full second, small waits (like ~160ms) also work.
⚙️ Calling `0x1d` repeatedly will provide an updated list of up to 4 hosts, always in the same order within each call. If more than 4 hosts are available, the game must track the IDs found and loop through the `0x1c`, `0x1d`, and `0x1e` sequence to discover additional hosts. Each iteration of this sequence provides up to 4 hosts in the order they are discovered by the wireless adapter.
⏳ If a client sends a `0x1c` and then starts a `0x1d` loop (1 command per frame), and a console that was broadcasting is turned off, it disappears after 3 seconds.
#### PollConnections - `0x1a`
#### AcceptConnections - `0x1a`
- Send length: 0, response length: 0+
- Polls new connections and returns a list with the connected adapters. The length of the response is zero if there are no connected adapters.
- It includes one value per connected client, in which the most significant byte is the `clientNumber` (see [IsConnectionComplete](#isconnectioncomplete---0x20)) and the least significant byte is the ID.
- 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.
`0x19`, `0x1a` and `0x1b` behave like the 3 broadcast reading commands (`0x1c`, `0x1d` and `0x1e`), in the sense that `StartHost` puts the adapter in 'open host' mode, `PollConnections` polls new connections and `EndHost` exits the open host mode.
#### Connect - `0x1f`
[![Image without alt text or caption](img/wireless/0x1f.png)](img/wireless/0x1f.png)
@ -313,7 +302,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
- Send length: 1, response length: 0
- Send the ID of the adapter you want to connect to from [BroadcastRead](#broadcastread---0x1c-0x1d-and-0x1e).
#### IsConnectionComplete - `0x20`
#### IsFinishedConnect - `0x20`
[![Image without alt text or caption](img/wireless/0x20.png)](img/wireless/0x20.png)
@ -329,7 +318,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
[![Image without alt text or caption](img/wireless/0x21.png)](img/wireless/0x21.png)
- Send length: 0, response length: 1
- Called after [IsConnectionComplete](#isconnectioncomplete---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
- 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`
@ -340,7 +329,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
- For hosts: the number of `bytes` that come next. For example, if we want to send `0xaabbccdd` and `0x12345678` in the same command, we need to send:
- `0x00000008`, `0xaabbccdd`, `0x12345678`.
- For clients: `(bytes << (3 + (1+clientNumber) * 5))`. The `clientNumber` is what I described in [IsConnectionComplete](#isconnectioncomplete---0x20). For example, if we want to send a single 4-byte value (`0xaabbccdd`):
- For clients: `(bytes << (3 + (1+clientNumber) * 5))`. The `clientNumber` is what I described in [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`
@ -378,7 +367,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
- **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), up to 4. This is probably just garbage that stays in the hardware buffer, but since clients cannot take initiative, some games send 1 byte and no data on the server side to let clients talk. Until we have a better name, we'll call this **ghost sends**.
🔁 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`
@ -423,7 +412,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
[![Image without alt text or caption](img/wireless/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 `PollConnections` calls and its `clientNumber` will be liberated, so other peers can connect.
- 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:
@ -461,8 +450,7 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
* Bits `16-23`: A 4-bit array with slots. If the console is a client, it'll have a 1 in the position assigned to that slot (e.g. the one with `clientNumber` 3 will have `0100`). The host will always have `0000` here.
* Bits `24-31`: A number indicating the state of the adapter
- `0` = idle
- `1` = serving (host), closed room
- `2` = serving (host), open room
- `1`/`2` = serving (host)
- `3` = searching
- `4` = connecting
- `5` = connected (client)
@ -471,10 +459,10 @@ Let's call these `BroadcastReadStart`, `BroadcastReadPoll`, and `BroadcastReadEn
- Send length: 0, Response length: 1+
- It's returns a list of the connected adapters, similar to what `PollConnections` 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 `PollConnections` fails.
- `SlotStatus` can be called after `EndHost`, while `AcceptConnections` fails.
#### ConfigStatus - `0x15`
@ -527,7 +515,7 @@ If we analyze whether a command ID throws an 'invalid command' error (`0x996601e
- The extra parameter has two bitarrays:
- Bits `0-4`: The clients that _received_ data.
- Bits `8-11`: The clients marked as _inactive_. This depends on the # of maximum transmissions configured with the [Setup](#setup---0x17) command. It only marks them as inactive after 4 seconds.
- Bits `8-11`: The clients marked as _inactive_. This depends on the # of maximum transmissions configured with the [Setup](#setup---0x17) command.
🔗 When the adapter is disconnected from the host, it sends a `0x99660029`.
@ -539,16 +527,12 @@ If we analyze whether a command ID throws an 'invalid command' error (`0x996601e
While the clock is inverted, the acknowledge procedure is 'standard' but with the inverted roles
[![during clock inversion](img/wireless/ack-inverted.png)](img/wireless/ack-inverted.png)
1. The adapter goes low as soon as it can.
2. The GBA goes high.
3. The adapter goes high.
4. The GBA goes low _when its ready_, but **wait at least 40us**! (\*)
5. The adapter goes low when it's ready.
6. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
> (\*) Clock inversion is _finicky_. If you don't wait enough time between transfers, the adapter will desync _forever_ (well, until you reset it with _SD=HIGH_). `LinkWireless` doesn't use wait commands and calls the regular `SendData` instead, it's less efficient but way more reliable.
1. The adapter goes low as soon as it can.
2. The GBA goes high.
3. The adapter goes high.
4. The GBA goes low _when its ready_.
5. The adapter goes low when it's ready.
6. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
## Wireless Multiboot
@ -556,15 +540,13 @@ While the clock is inverted, the acknowledge procedure is 'standard' but with th
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.
5. Since the adapter hardware is still connected, the games 'restore' the SDK state to preserve the session and avoid reconnecting clients. The `0x13` command returns whether we are a server or a client, as well as the client number.
- 4. The host sends a 'rom end' command and the games boot.
### Valid header
@ -631,7 +613,6 @@ enum CommState : unsigned int {
- Transfers can contain more than one packet.
- As the maximum transfer lengths are `87` (server) and `16` (client), based on header sizes, the maximum payload lengths are `84` and `14`.
- The `targetSlots` field inside the server header is a bit array that indicates which clients the message is directed to. E.g. `0b0100` means 'client 2 only' and `0b1111` means 'all clients'.
- In `ServerSDKHeader` and `ClientSDKHeader`, all the non-documented bits (including `_unused_`) should be `0`. Otherwise, the official SDK might not respond!
### (1) Client handshake
@ -651,23 +632,19 @@ enum CommState : unsigned int {
- Server: ACKs the packet
- Client: sends `0x424D08A6`, `0x004C442D`
- Header: `0x08A6` (`size=6, n=1, ph=1, ack=0, commState=2`)
- Payload: `0x4D`, `0x42`, `0x2D`, `0x44`, `0x4C`, `0x00`
- => `MB-DL`
- Payload: `MB-DL`
- Server: ACKs the packet
- Client: sends `0x000008C6`, `0x50000000`
- Header: `0x08C6` (`size=6, n=1, ph=2, ack=0, commState=2`)
- Payload: `0x00`, `0x00`, `0x00`, `0x00`, `0x00`, `0x00`, `0x50`
- => `P`
- Payload: `P`
- Server: ACKs the packet
- Client: sends `0x414C08E6`, `0x20524559`
- Header: `0x08E6` (`size=6, n=1, ph=3, ack=0, commState=2`)
- Payload: `0x4C`, `0x41`, `0x59`, `0x45`, `0x52`, `0x20`
- => `LAYER `
- Payload: `LAYER`
- Server: ACKs the packet
- Client: sends `0x00410902`
- Header: `0x0902` (`size=2, n=2, ph=0, ack=0, commState=2`)
- Payload: `0x41`, `0x00`
- => `A`
- Payload: `A`
- Server: ACKs the packet
- Client: sends `0x00000C00`
- Header: `0x0C00` (`size=0, n=0, ph=0, ack=0, commState=3`) (`3 = ENDING`)
@ -676,15 +653,13 @@ enum CommState : unsigned int {
- Client: sends `0x00000080`
- Header: `0x0080` (`size=0, n=1, ph=0, ack=0, commState=0`) (`0 = OFF`)
- No payload
- Server: Ghost send _(OFF state doesn't expect an ack!)_
- Server: ACKs the packet
## (2) ROM start command
- Server: sends `0x00044807`, `0x00000054`, `0x00000002`
- Header: `0x044807` (`size=7, n=1, ph=0, ack=0, commState=1`) (`1 = STARTING`)
- Payload: these 7 bytes depend on the game
- _No No No Puzzle Chailien_ sends: `0x00`, `0x54`, `0x00`, `0x00`, `0x00`, `0x02`, `0x00`
- _Super Mario Bros Famicom Mini_ sends: `0x00`, `0x54`, `0x00`, `0xFC`, `0x44`, `0x01`, `0x00`
- Payload: `0x00`, `0x54`, `0x00`, `0x00`, `0x00`, `0x02`, `0x00`
- Client: ACKs the packet (`size=0, n=1, ph=0, ack=1, commState=1`)
## (3) ROM bytes
@ -700,7 +675,6 @@ 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`)
- _OFF state, so this one is not acknowledged by the clients!_
## SPI config

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code ../_lib/libgbfs
SRCDIRS := src ../_lib ../../lib ../_lib/libgbfs
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -1,9 +1,9 @@
// (0) Include the header
#include "../../../lib/LinkCableMultiboot.hpp"
#include <cstring>
#include "../../_lib/common.h"
#include "../../_lib/interrupt.h"
#include <string.h>
#include <tonc.h>
#include <string>
extern "C" {
#include "../../_lib/libgbfs/gbfs.h"
@ -13,6 +13,10 @@ 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);
// (1) Create a LinkCableMultiboot instance
LinkCableMultiboot* linkCableMultiboot = new LinkCableMultiboot();
@ -29,10 +33,11 @@ void selectRight() {
}
void init() {
Common::initTTE();
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
interrupt_init();
interrupt_add(INTR_VBLANK, []() {});
irq_init(NULL);
irq_add(II_VBLANK, NULL);
}
int main() {
@ -40,16 +45,16 @@ int main() {
// Ensure there are GBFS files
if (fs == NULL) {
Common::log("! GBFS file not found");
log("! GBFS file not found");
while (true)
;
} else if (gbfs_get_nth_obj(fs, 0, NULL, NULL) == NULL) {
Common::log("! No files found (GBFS)");
log("! No files found (GBFS)");
while (true)
;
}
bool left = true, right = true, a = true, b = true, l = true;
bool left = false, right = false, a = false, b = false, l = false;
while (true) {
// Get selected ROM name
@ -65,18 +70,17 @@ int main() {
}
// Toggle mode
if (Common::didPress(KEY_L, l))
if (didPress(KEY_L, l))
spi = !spi;
// Select ROM
if (Common::didPress(KEY_LEFT, left))
if (didPress(KEY_LEFT, left))
selectLeft();
if (Common::didPress(KEY_RIGHT, right))
if (didPress(KEY_RIGHT, right))
selectRight();
// Menu
Common::log(
"LinkCableMultiboot_demo\n (v8.0.3)\n\n"
log("LinkCableMultiboot_demo\n (v7.0.1)\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 " +
@ -84,8 +88,8 @@ int main() {
std::string(spi ? "SPI (GBC cable)" : "MULTI_PLAY (GBA cable)"));
// Send ROM
if (Common::didPress(KEY_A, a)) {
Common::log("Sending... (SELECT to cancel)");
if (didPress(KEY_A, a)) {
log("Sending... (SELECT to cancel)");
// (2) Send the ROM
auto result = linkCableMultiboot->sendRom(
@ -98,14 +102,14 @@ int main() {
: LinkCableMultiboot::TransferMode::MULTI_PLAY);
// Print results and wait
Common::log("Result: " + std::to_string((int)result) + "\n" +
"Press DOWN to continue...");
Common::waitForKey(KEY_DOWN);
log("Result: " + std::to_string(result) + "\n" +
"Press DOWN to continue...");
waitFor(KEY_DOWN);
}
// Launch ROM
if (Common::didPress(KEY_B, b)) {
Common::log("Launching...");
if (didPress(KEY_B, b)) {
log("Launching...");
VBlankIntrWait();
REG_IME = 0;
@ -115,7 +119,7 @@ int main() {
(const u8*)gbfs_get_nth_obj(fs, selectedFile, NULL, &fileLength);
void* EWRAM = (void*)0x02000000;
std::memcpy(EWRAM, romToSend, fileLength);
memcpy(EWRAM, romToSend, fileLength);
asm volatile(
"mov r0, %0\n"
@ -133,3 +137,28 @@ int main() {
return 0;
}
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;
}

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -4,20 +4,27 @@
// (0) Include the header
#include "../../../lib/LinkCable.hpp"
#include "../../_lib/common.h"
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
void log(std::string text);
// (1) Create a LinkCable instance
LinkCable* linkCable = new LinkCable();
void init() {
Common::initTTE();
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
// (2) Add the required interrupt service routines
interrupt_init();
interrupt_add(INTR_VBLANK, LINK_CABLE_ISR_VBLANK);
interrupt_add(INTR_SERIAL, LINK_CABLE_ISR_SERIAL);
interrupt_add(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
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);
// (3) Initialize the library
linkCable->activate();
@ -38,7 +45,7 @@ int main() {
u16 keys = ~REG_KEYS & KEY_ANY;
linkCable->send(keys + 1); // (avoid using 0)
std::string output = "LinkCable_basic (v8.0.3)\n\n";
std::string output = "LinkCable_basic (v7.0.1)\n\n";
if (linkCable->isConnected()) {
u8 playerCount = linkCable->playerCount();
u8 currentPlayerId = linkCable->currentPlayerId();
@ -60,8 +67,14 @@ int main() {
}
VBlankIntrWait();
Common::log(output);
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}

View File

@ -123,8 +123,9 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba -lgba-sprite-engine
BUILD := build
SRCDIRS := src ../_lib ../_lib/libgba-sprite-engine ../../lib ../../lib/iwram_code \
src/scenes
SRCDIRS := src ../_lib ../../lib \
src/scenes \
src/utils
DATADIRS :=
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
@ -165,12 +166,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -4,8 +4,8 @@
#include "main.h"
#include <libgba-sprite-engine/gba_engine.h>
#include "../../_lib/interrupt.h"
#include "../../_lib/libgba-sprite-engine/scene.h"
#include "scenes/TestScene.h"
#include "utils/SceneUtils.h"
void setUpInterrupts();
void printTutorial();
@ -40,74 +40,18 @@ int main() {
DEBULOG("! started");
}
// log player ID/count and debug flags
static constexpr int BIT_READY = 3;
static constexpr int BIT_ERROR = 6;
static constexpr int BIT_START = 7;
#ifndef USE_LINK_UNIVERSAL
// log player ID/count and important flags
TextStream::instance().setText(
"P" + std::to_string(linkConnection->currentPlayerId()) + "/" +
std::to_string(linkConnection->playerCount()) + " R" +
std::to_string(Common::isBitHigh(REG_SIOCNT, BIT_READY)) + "-S" +
std::to_string(Common::isBitHigh(REG_SIOCNT, BIT_ERROR)) + "-E" +
std::to_string(Common::isBitHigh(REG_SIOCNT, BIT_START)) +
(linkConnection->didQueueOverflow(false) ? "!" : ""),
0, -3);
#else
if (linkConnection->isConnected()) {
if (linkConnection->getMode() == LinkUniversal::Mode::LINK_CABLE) {
auto readyToSyncMessages =
linkConnection->getLinkCable()->_state.readyToSyncMessages;
auto newMessages = linkConnection->getLinkCable()->_state.newMessages;
u32 readyToSyncSize =
readyToSyncMessages[0].size() + readyToSyncMessages[1].size() +
readyToSyncMessages[2].size() + readyToSyncMessages[3].size();
u32 newSize = newMessages[0].size() + newMessages[1].size() +
newMessages[2].size() + newMessages[3].size();
TextStream::instance().setText(
"P" + std::to_string(linkConnection->currentPlayerId()) + "/" +
std::to_string(linkConnection->playerCount()) + " >" +
std::to_string(linkConnection->getLinkCable()
->_state.outgoingMessages.size()) +
" <" + std::to_string(readyToSyncSize) + " <<" +
std::to_string(newSize) + " / R" +
std::to_string(Common::isBitHigh(REG_SIOCNT, BIT_READY)) +
"-S" +
std::to_string(Common::isBitHigh(REG_SIOCNT, BIT_ERROR)) +
"-E" +
std::to_string(Common::isBitHigh(REG_SIOCNT, BIT_START)) +
(linkConnection->didQueueOverflow(false) ? "!" : ""),
0, -3);
} else {
TextStream::instance().setText(
"P" + std::to_string(linkConnection->currentPlayerId()) + "/" +
std::to_string(linkConnection->playerCount()) + " >" +
std::to_string(linkConnection->getLinkWireless()
->sessionState.newOutgoingMessages.size()) +
" >>" +
std::to_string(linkConnection->getLinkWireless()
->sessionState.outgoingMessages.size()) +
" <" +
std::to_string(linkConnection->getLinkWireless()
->sessionState.incomingMessages.size()) +
" <<" +
std::to_string(linkConnection->getLinkWireless()
->sessionState.newIncomingMessages.size()) +
(linkConnection->didQueueOverflow(false) ? "!" : ""),
0, -3);
}
} else {
TextStream::instance().setText(
"P" + std::to_string(linkConnection->currentPlayerId()) + "/" +
std::to_string(linkConnection->playerCount()) + " [" +
std::to_string((int)linkConnection->getState()) + "]<" +
std::to_string((int)linkConnection->getMode()) + ">(" +
std::to_string((int)linkConnection->getWirelessState()) + ") w(" +
std::to_string(linkConnection->_getWaitCount()) + ") sw(" +
std::to_string(linkConnection->_getSubWaitCount()) + ")",
0, -3);
}
#endif
"P" + asStr(linkConnection->currentPlayerId()) + "/" +
asStr(linkConnection->playerCount()) + "-R" +
asStr(isBitHigh(REG_SIOCNT, BIT_READY)) + "-S" +
asStr(isBitHigh(REG_SIOCNT, BIT_ERROR)) + "-E" +
asStr(isBitHigh(REG_SIOCNT, BIT_START)),
0, 14);
engine->update();
@ -117,33 +61,43 @@ int main() {
return 0;
}
inline void ISR_reset() {
RegisterRamReset(RESET_REG | RESET_VRAM);
SoftReset();
}
inline void setUpInterrupts() {
interrupt_init();
#ifndef USE_LINK_UNIVERSAL
// LinkCable
interrupt_add(INTR_VBLANK, LINK_CABLE_ISR_VBLANK);
interrupt_add(INTR_SERIAL, LINK_CABLE_ISR_SERIAL);
interrupt_add(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
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);
#else
// LinkUniversal
interrupt_add(INTR_VBLANK, LINK_UNIVERSAL_ISR_VBLANK);
interrupt_add(INTR_SERIAL, LINK_UNIVERSAL_ISR_SERIAL);
interrupt_add(INTR_TIMER3, LINK_UNIVERSAL_ISR_TIMER);
interrupt_set_handler(INTR_VBLANK, LINK_UNIVERSAL_ISR_VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_SERIAL, LINK_UNIVERSAL_ISR_SERIAL);
interrupt_enable(INTR_SERIAL);
interrupt_set_handler(INTR_TIMER3, LINK_UNIVERSAL_ISR_TIMER);
interrupt_enable(INTR_TIMER3);
#endif
// A+B+START+SELECT = SoftReset
#if MULTIBOOT_BUILD == 0
// A+B+START+SELECT
REG_KEYCNT = 0b1100000000001111;
interrupt_add(INTR_KEYPAD, Common::ISR_reset);
#endif
interrupt_set_handler(INTR_KEYPAD, ISR_reset);
interrupt_enable(INTR_KEYPAD);
}
void printTutorial() {
#ifndef USE_LINK_UNIVERSAL
DEBULOG("LinkCable_full (v8.0.3)");
DEBULOG("LinkCable_full (v7.0.1)");
#else
DEBULOG("LinkUniversal_full (v8.0.3)");
DEBULOG("LinkUniversal_full (v7.0.1)");
#endif
DEBULOG("");
@ -154,10 +108,7 @@ void printTutorial() {
DEBULOG("A: send counter++ (cont)");
DEBULOG("L: send counter++ twice (once)");
DEBULOG("R: send counter++ twice (cont)");
#ifdef USE_LINK_UNIVERSAL
DEBULOG("RIGHT: get signal level");
#endif
DEBULOG("SELECT: force lag (5 frames)");
DEBULOG("SELECT: force lag (9k lines)");
DEBULOG("DOWN: turn off connection");
DEBULOG("");
}

View File

@ -3,17 +3,13 @@
// #define USE_LINK_UNIVERSAL
#define LINK_CABLE_DEBUG_MODE
#define LINK_WIRELESS_DEBUG_MODE
#ifndef USE_LINK_UNIVERSAL
#include "../../../lib/LinkCable.hpp"
#else
#include "../../../lib/LinkUniversal.hpp"
#endif
#include "../../_lib/common.h"
#include "../../_lib/libgba-sprite-engine/scene.h"
#include <tonc.h>
#ifndef USE_LINK_UNIVERSAL
extern LinkCable* linkConnection;

View File

@ -3,6 +3,8 @@
#include <libgba-sprite-engine/background/text_stream.h>
#include "../main.h"
#include "utils/InputHandler.h"
#include "utils/SceneUtils.h"
TestScene::TestScene(std::shared_ptr<GBAEngine> engine) : Scene(engine) {}
@ -16,16 +18,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> rightHandler =
std::unique_ptr<InputHandler>(new InputHandler());
inline void send(u16 data) {
DEBULOG("-> " + std::to_string(data));
DEBULOG("-> " + asStr(data));
linkConnection->send(data);
}
void printWirelessSignalLevel();
std::vector<Background*> TestScene::backgrounds() {
return {};
}
@ -55,27 +53,22 @@ void TestScene::tick(u16 keys) {
lHandler->setIsPressed(keys & KEY_L);
rHandler->setIsPressed(keys & KEY_R);
selectHandler->setIsPressed(keys & KEY_SELECT);
rightHandler->setIsPressed(keys & KEY_RIGHT);
// log events
if (!isConnected && linkConnection->isConnected()) {
isConnected = true;
initialized = false;
DEBULOG("! connected (" + std::to_string(linkConnection->playerCount()) +
DEBULOG("! connected (" + asStr(linkConnection->playerCount()) +
" players)");
}
if (isConnected && !linkConnection->isConnected()) {
isConnected = false;
DEBULOG("! disconnected");
}
// other buttons
if (selectHandler->getIsPressed()) {
if (selectHandler->hasBeenPressedNow()) {
DEBULOG("! lagging...");
Link::wait(228 * 5);
SCENE_wait(9000);
}
if (rightHandler->hasBeenReleasedNow())
printWirelessSignalLevel();
// determine which value should be sent
u16 value = LINK_CABLE_NO_DATA;
@ -105,38 +98,9 @@ void TestScene::tick(u16 keys) {
while (linkConnection->canRead(i)) {
u16 message = linkConnection->read(i);
if (i != linkConnection->currentPlayerId())
DEBULOG("<-p" + std::to_string(i) + ": " + std::to_string(message) +
" (frame " + std::to_string(frameCounter) + ")");
DEBULOG("<-p" + asStr(i) + ": " + asStr(message) + " (frame " +
asStr(frameCounter) + ")");
}
}
}
}
void printWirelessSignalLevel() {
#ifdef USE_LINK_UNIVERSAL
if (linkConnection->getMode() != LinkUniversal::Mode::LINK_WIRELESS) {
DEBULOG("! not in wireless mode");
return;
}
LinkWireless::SignalLevelResponse response;
if (!linkConnection->getLinkWireless()->getSignalLevel(response)) {
DEBULOG(linkConnection->getLinkWireless()->getLastError() ==
LinkWireless::Error::BUSY_TRY_AGAIN
? "! busy, try again"
: "! failed");
return;
}
if (linkConnection->getLinkWireless()->getState() ==
LinkWireless::State::SERVING) {
for (u32 i = 1; i < linkConnection->playerCount(); i++)
DEBULOG("P" + std::to_string(i) + ": " +
std::to_string(response.signalLevels[i] * 100 / 255) + "%");
} else {
auto playerId = linkConnection->currentPlayerId();
DEBULOG("P" + std::to_string(playerId) + ": " +
std::to_string(response.signalLevels[playerId] * 100 / 255) + "%");
}
#endif
}

View File

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

View File

@ -1,4 +1,4 @@
#include "scene.h"
#include "SceneUtils.h"
#include <libgba-sprite-engine/gba_engine.h>

View File

@ -1,5 +1,5 @@
#ifndef LINK_EXAMPLES_SCENE_H
#define LINK_EXAMPLES_SCENE_H
#ifndef SCENE_UTILS_H
#define SCENE_UTILS_H
#include <libgba-sprite-engine/background/text_stream.h>
#include <tonc_memdef.h>
@ -11,38 +11,13 @@ const u32 TEXT_MIDDLE_COL = 12;
extern int DEBULOG_LINE;
void DEBULOG(std::string string);
class InputHandler {
public:
InputHandler() {
this->isPressed = false;
this->isWaiting = true;
}
inline std::string asStr(u16 data) {
return std::to_string(data);
}
bool getIsPressed() { return isPressed; }
bool hasBeenPressedNow() { return isNewPressEvent; }
bool hasBeenReleasedNow() { return isNewReleaseEvent; }
bool getHandledFlag() { return handledFlag; }
void setHandledFlag(bool value) { handledFlag = value; }
void setIsPressed(bool isPressed) {
bool isNewPressEvent = !this->isWaiting && !this->isPressed && isPressed;
bool isNewReleaseEvent = !this->isWaiting && this->isPressed && !isPressed;
this->isPressed = isPressed;
this->isWaiting = this->isWaiting && isPressed;
this->isNewPressEvent = isNewPressEvent;
this->isNewReleaseEvent = isNewReleaseEvent;
}
protected:
bool isPressed = false;
bool isNewPressEvent = false;
bool isNewReleaseEvent = false;
bool handledFlag = false;
bool isWaiting = false;
};
inline bool isBitHigh(u16 data, u8 bit) {
return (data >> bit) & 1;
}
inline void BACKGROUND_enable(bool bg0, bool bg1, bool bg2, bool bg3) {
REG_DISPCNT = bg0 ? REG_DISPCNT | DCNT_BG0 : REG_DISPCNT & ~DCNT_BG0;
@ -69,4 +44,16 @@ inline void SCENE_write(std::string text, u32 row) {
TEXT_MIDDLE_COL - text.length() / 2);
}
#endif // LINK_EXAMPLES_SCENE_H
inline void SCENE_wait(u32 verticalLines) {
u32 count = 0;
u32 vCount = REG_VCOUNT;
while (count < verticalLines) {
if (REG_VCOUNT != vCount) {
count++;
vCount = REG_VCOUNT;
}
};
}
#endif // SCENE_UTILS_H

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src lib
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -5,6 +5,7 @@
// - The units will start running at the same time when both receive a 1.
// - When a GBA receives something not equal to previousValue + 1, it hangs.
// - It should continue until reaching 65534, with no packet loss.
// - The user can purposely mess up the sync by pressing START to add lag.
// B) Packet sync test:
// - Like (A), but using synchronous transfers.
// - The test will ensure the remote counters match local counters.
@ -12,11 +13,9 @@
// - Measures how much time it takes to receive a packet from the other node.
// R) Measure ping-pong latency:
// - Like (L), but adding a validation response and adding that time.
// Controls:
// - The user can purposely mess up the sync by pressing START to add lag.
// - The interval can be changed mid-test with the LEFT/RIGHT keys.
#include "main.h"
#include <string>
#include "../../_lib/interrupt.h"
#define FINAL_VALUE 65534
@ -24,15 +23,13 @@
void test(bool withSync);
void measureLatency(bool withPong);
void forceSync();
void log(std::string text);
void waitFor(u16 key);
void wait(u32 verticalLines);
bool needsReset();
u32 vblankTime = 0;
u32 serialTime = 0;
u32 timerTime = 0;
u32 vblankIRQs = 0;
u32 serialIRQs = 0;
u32 timerIRQs = 0;
u32 avgTime = 0;
void profileStart();
u32 profileStop();
u32 toMs(u32 cycles);
#ifndef USE_LINK_UNIVERSAL
LinkCable* linkCable = new LinkCable();
@ -42,7 +39,7 @@ LinkUniversal* linkUniversal =
new LinkUniversal(LinkUniversal::Protocol::AUTODETECT,
"LinkUniversal",
(LinkUniversal::CableOptions){
.baudRate = LinkCable::BaudRate::BAUD_RATE_1,
.baudRate = LinkCable::BAUD_RATE_1,
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
.interval = LINK_CABLE_DEFAULT_INTERVAL,
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
@ -51,131 +48,83 @@ LinkUniversal* linkUniversal =
.maxPlayers = 2,
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID});
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID},
__qran_seed);
LinkUniversal* linkConnection = linkUniversal;
#endif
u16 getInterval() {
#ifndef USE_LINK_UNIVERSAL
return linkConnection->config.interval;
#else
return linkConnection->getLinkCable()->config.interval;
#endif
}
void init() {
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
void setInterval(u16 interval) {
#ifndef USE_LINK_UNIVERSAL
linkConnection->config.interval = interval;
linkConnection->resetTimer();
#else
linkConnection->getLinkCable()->config.interval = interval;
linkConnection->getLinkWireless()->config.interval = interval;
linkConnection->resetTimer();
#endif
}
void setUpInterrupts(bool profiler) {
vblankIRQs = 0;
vblankTime = 0;
serialTime = 0;
timerTime = 0;
serialIRQs = 0;
timerIRQs = 0;
interrupt_init();
#ifndef USE_LINK_UNIVERSAL
// LinkCable
interrupt_add(INTR_VBLANK, profiler ? []() {
Common::profileStart();
LINK_CABLE_ISR_VBLANK();
vblankTime += Common::profileStop();
vblankIRQs++;
} : LINK_CABLE_ISR_VBLANK);
interrupt_add(INTR_SERIAL, profiler ? []() {
Common::profileStart();
LINK_CABLE_ISR_SERIAL();
serialTime += Common::profileStop();
serialIRQs++;
} : LINK_CABLE_ISR_SERIAL);
interrupt_add(INTR_TIMER3, profiler ? []() {
Common::profileStart();
LINK_CABLE_ISR_TIMER();
timerTime += Common::profileStop();
timerIRQs++;
} : LINK_CABLE_ISR_TIMER);
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);
#else
// LinkUniversal
interrupt_add(INTR_VBLANK, profiler ? []() {
Common::profileStart();
LINK_UNIVERSAL_ISR_VBLANK();
vblankTime += Common::profileStop();
vblankIRQs++;
} : LINK_UNIVERSAL_ISR_VBLANK);
interrupt_add(INTR_SERIAL, profiler ? []() {
Common::profileStart();
LINK_UNIVERSAL_ISR_SERIAL();
serialTime += Common::profileStop();
serialIRQs++;
} : LINK_UNIVERSAL_ISR_SERIAL);
interrupt_add(INTR_TIMER3, profiler ? []() {
Common::profileStart();
LINK_UNIVERSAL_ISR_TIMER();
timerTime += Common::profileStop();
timerIRQs++;
} : LINK_UNIVERSAL_ISR_TIMER);
interrupt_set_handler(INTR_VBLANK, LINK_UNIVERSAL_ISR_VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_SERIAL, LINK_UNIVERSAL_ISR_SERIAL);
interrupt_enable(INTR_SERIAL);
interrupt_set_handler(INTR_TIMER3, LINK_UNIVERSAL_ISR_TIMER);
interrupt_enable(INTR_TIMER3);
#endif
}
void init() {
Common::initTTE();
interrupt_init();
setUpInterrupts(false);
}
int main() {
init();
while (true) {
#ifndef USE_LINK_UNIVERSAL
std::string output = "LinkCable_stress (v8.0.3)\n\n";
std::string output = "LinkCable_stress (v7.0.1)\n\n";
#else
std::string output = "LinkUniversal_stress (v8.0.3)\n\n";
Link::randomSeed = __qran_seed;
std::string output = "LinkUniversal_stress (v7.0.1)\n\n";
#endif
linkConnection->deactivate();
output +=
"A: Test packet loss\nB: Test packet sync\nL: Measure ping latency\nR: "
"Measure ping-pong latency\n\nHold DOWN: Initial t=100\nHold UP: "
"Initial t=25\n\nLEFT/RIGHT: Change t\nSTART: Add lag\nSELECT: Reset ";
Common::log(output);
"Measure ping-pong latency\n\nLEFT: t=100\nRIGHT: t=25\nDOWN: "
"t=200\nUP: t=10\nSTART: Add lag\nSELECT: Reset ";
log(output);
Common::waitForKey(KEY_A | KEY_B | KEY_L | KEY_R);
waitFor(KEY_A | KEY_B | KEY_L | KEY_R);
u16 initialKeys = ~REG_KEYS & KEY_ANY;
u32 interval = 50;
if (initialKeys & KEY_DOWN)
if (initialKeys & KEY_LEFT)
interval = 100;
if (initialKeys & KEY_UP)
if (initialKeys & KEY_RIGHT)
interval = 25;
setInterval(interval);
if (initialKeys & KEY_DOWN)
interval = 200;
if (initialKeys & KEY_UP)
interval = 10;
#ifndef USE_LINK_UNIVERSAL
linkConnection->config.interval = interval;
#else
linkConnection->linkCable->config.interval = interval;
linkConnection->linkWireless->config.interval = interval;
#endif
linkConnection->activate();
if (initialKeys & KEY_A) {
setUpInterrupts(true);
if (initialKeys & KEY_A)
test(false);
} else if (initialKeys & KEY_B) {
setUpInterrupts(true);
else if (initialKeys & KEY_B)
test(true);
} else if (initialKeys & KEY_L) {
setUpInterrupts(false);
else if (initialKeys & KEY_L)
measureLatency(false);
} else if (initialKeys & KEY_R) {
setUpInterrupts(false);
else if (initialKeys & KEY_R)
measureLatency(true);
}
}
return 0;
@ -186,47 +135,25 @@ void test(bool withSync) {
u16 expectedCounter = 0;
bool error = false;
u16 receivedRemoteCounter = 0;
bool increasingInterval = true;
bool decreasingInterval = true;
Common::log("Waiting for data...");
log("Waiting for data...");
while (true) {
if (needsReset())
return;
if (vblankIRQs >= 60) {
avgTime = (vblankTime + serialTime + timerTime) / 60;
vblankIRQs = 0;
vblankTime = 0;
serialTime = 0;
timerTime = 0;
serialIRQs = 0;
timerIRQs = 0;
}
u16 keys = ~REG_KEYS & KEY_ANY;
if (keys & KEY_START) {
Common::log("Lagging...");
Link::wait(1500);
}
if (Common::didPress(KEY_RIGHT, increasingInterval) &&
getInterval() < 200) {
setInterval(getInterval() + 5);
linkConnection->resetTimer();
}
if (Common::didPress(KEY_LEFT, decreasingInterval) && getInterval() > 5) {
setInterval(getInterval() - 5);
linkConnection->resetTimer();
}
linkConnection->sync();
auto playerCount = linkConnection->playerCount();
std::string output = "";
if (linkConnection->isConnected() && playerCount == 2) {
u16 keys = ~REG_KEYS & KEY_ANY;
if (keys & KEY_START) {
log("Lagging...");
wait(1500);
}
auto currentPlayerId = linkConnection->currentPlayerId();
auto remotePlayerId = !currentPlayerId;
@ -270,9 +197,7 @@ void test(bool withSync) {
}
}
output += "(" + std::to_string(localCounter) + ", " +
std::to_string(expectedCounter) +
")\n\ninterval = " + std::to_string(getInterval()) +
"\ncyc/frm = " + std::to_string(avgTime);
std::to_string(expectedCounter) + ")\n";
} else {
output += "Waiting...";
localCounter = 0;
@ -282,14 +207,14 @@ void test(bool withSync) {
}
VBlankIntrWait();
Common::log(output);
log(output);
if (error) {
while (true)
if (needsReset())
return;
} else if (localCounter == FINAL_VALUE && expectedCounter == FINAL_VALUE) {
Common::log("Test passed!");
log("Test passed!");
while (true)
if (needsReset())
return;
@ -298,36 +223,17 @@ void test(bool withSync) {
}
void measureLatency(bool withPong) {
Common::log("Waiting for data...");
log("Waiting for data...");
bool didInitialize = false;
u32 counter = 0;
u32 samples = 0;
u32 totalMs = 0;
bool increasingInterval = false;
bool decreasingInterval = false;
while (true) {
if (needsReset())
return;
u16 keys = ~REG_KEYS & KEY_ANY;
if (keys & KEY_START) {
Common::log("Lagging...");
Link::wait(1500);
}
if (Common::didPress(KEY_RIGHT, increasingInterval) &&
getInterval() < 200) {
setInterval(getInterval() + 5);
linkConnection->resetTimer();
counter = samples = totalMs = 0;
}
if (Common::didPress(KEY_LEFT, decreasingInterval) && getInterval() > 5) {
setInterval(getInterval() - 5);
linkConnection->resetTimer();
counter = samples = totalMs = 0;
}
linkConnection->sync();
auto playerCount = linkConnection->playerCount();
@ -344,36 +250,36 @@ void measureLatency(bool withPong) {
u32 sentPacket = ++counter;
Common::profileStart();
profileStart();
linkConnection->send(sentPacket);
if (!linkConnection->waitFor(remotePlayerId, needsReset)) {
Common::log("No response! (1) Press DOWN");
Common::profileStop();
Common::waitForKey(KEY_DOWN);
log("No response! (1) Press DOWN");
profileStop();
waitFor(KEY_DOWN);
return;
}
u16 receivedPacket = linkConnection->read(remotePlayerId);
if (withPong) {
linkConnection->send(receivedPacket);
if (!linkConnection->waitFor(remotePlayerId, needsReset)) {
Common::log("No response! (2) Press DOWN");
Common::profileStop();
Common::waitForKey(KEY_DOWN);
log("No response! (2) Press DOWN");
profileStop();
waitFor(KEY_DOWN);
return;
}
u16 validation = linkConnection->read(remotePlayerId);
if (validation != sentPacket) {
Common::log("Invalid response! Press DOWN\n value = " +
std::to_string(validation) +
"\n expected = " + std::to_string(sentPacket));
Common::profileStop();
Common::waitForKey(KEY_DOWN);
log("Invalid response! Press DOWN\n value = " +
std::to_string(validation) +
"\n expected = " + std::to_string(sentPacket));
profileStop();
waitFor(KEY_DOWN);
return;
}
}
u32 elapsedCycles = Common::profileStop();
u32 elapsedCycles = profileStop();
u32 elapsedMilliseconds = Common::toMs(elapsedCycles);
u32 elapsedMilliseconds = toMs(elapsedCycles);
samples++;
totalMs += elapsedMilliseconds;
u32 average = Div(totalMs, samples);
@ -382,13 +288,12 @@ void measureLatency(bool withPong) {
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) +
"\n\ninterval = " + std::to_string(getInterval());
"\nValue sent:\n " + std::to_string(sentPacket);
VBlankIntrWait();
Common::log(output);
log(output);
} else {
VBlankIntrWait();
Common::log("Waiting...");
log("Waiting...");
}
}
}
@ -403,7 +308,56 @@ void forceSync() {
linkConnection->read(remotePlayerId);
}
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));
}
void wait(u32 verticalLines) {
u32 count = 0;
u32 vCount = REG_VCOUNT;
while (count < verticalLines) {
if (REG_VCOUNT != vCount) {
count++;
vCount = REG_VCOUNT;
}
};
}
bool needsReset() {
u16 keys = ~REG_KEYS & KEY_ANY;
return keys & KEY_SELECT;
}
void profileStart() {
REG_TM1CNT_L = 0;
REG_TM2CNT_L = 0;
REG_TM1CNT_H = 0;
REG_TM2CNT_H = 0;
REG_TM2CNT_H = TM_ENABLE | TM_CASCADE;
REG_TM1CNT_H = TM_ENABLE | TM_FREQ_1;
}
u32 profileStop() {
REG_TM1CNT_H = 0;
REG_TM2CNT_H = 0;
return (REG_TM1CNT_L | (REG_TM2CNT_L << 16));
}
u32 toMs(u32 cycles) {
// CPU Frequency * time per frame = cycles per frame
// 16780000 * (1/60) ~= 279666
return (cycles * 1000) / (279666 * 60);
}

View File

@ -9,7 +9,7 @@
#include "../../../lib/LinkUniversal.hpp"
#endif
#include "../../_lib/common.h"
#include <tonc.h>
#ifndef USE_LINK_UNIVERSAL
extern LinkCable* linkConnection;

View File

@ -1,24 +0,0 @@
# 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
charset = shiftjis

View File

@ -1,12 +0,0 @@
.vscode
*.bin
*.bmp
*.elf
*.gba
*.lnk
*.o
*.raw
*.sa1
*.sav
*.vpk
*.loader

View File

@ -1,87 +0,0 @@
# //--------------------------------------------------------//
# // Based on Nintendo e-Reader "Hello World" Example (GBA) //
# // (c) 2004 Tim Schuerewegen //
# //--------------------------------------------------------//
# CHANGE THESE PATHS!
DEVKITPRO=/c/devkitPro
EREADER=/c/devkitPro/_e-reader
GCC_VER=14.1.0
PATH = /bin:$(EREADER):$(DEVKITPRO)/bin:$(DEVKITPRO)/devkitARM/bin
PATH_LIB_GCC = $(DEVKITPRO)/devkitARM/lib/gcc/arm-none-eabi/$(GCC_VER)/thumb
ifeq ($(JAP),1)
NAME = ローダー
REGION = 2
CFLAGS_EXTRA = -DREGION_JAP
ifeq ($(ENG),1)
CFLAGS_EXTRA += -DLANGUAGE_ENG
endif
else
NAME = DLC Loader
REGION = 1
CFLAGS_EXTRA =
endif
# assemble and link
GCC = arm-none-eabi-gcc.exe
AS = arm-none-eabi-as.exe
LD = arm-none-eabi-ld.exe
OBJCOPY = arm-none-eabi-objcopy.exe
# e-reader tools
NEVPK = nevpk.exe
NESAV = neflmake.exe
NEDCMAKE = nedcmake.exe
NEDCENC = nedcenc.exe
RAW2BMP = raw2bmp.exe
.PHONY: clean
all : sav bmp loader
sav : main.sav
bmp : main.vpk
$(NEDCMAKE) -i $< -type 2 -bin -type 2 -region $(REGION) -name "$(NAME)" -fill 1 -save 1 -o "main.bin"
$(NEDCENC) -i "main.bin" -o "main.raw"
$(RAW2BMP) -i "main.raw" -o "main.bmp"
main.sav : main.vpk
$(NESAV) -i $< -o "$@" -type 2 -name "$(NAME)"
main.vpk : main.bin
$(NEVPK) -i "$<" -o "$@" -c -level 0
main.bin : main.elf
$(OBJCOPY) -O binary "$<" "$@"
main.elf : crt0.o main.o ereader.ld
$(LD) crt0.o main.o -lgcc -L $(PATH_LIB_GCC) -T ereader.ld -O3 -o "$@"
main.o : src/main.c
$(GCC) -mthumb $(CFLAGS_EXTRA) -c -O3 -o "$@" "$<"
crt0.o : crt0.s
$(AS) -o "$@" "$<"
loader: main.loader
# `main.loader` contains the bytes that end up being sent via Link Cable
# It reads 4 bytes at offset 0x1002C as a little-endian UInt32 (the size),
# then copies from offset 0x10000 until (0x10000 + (0x002C+4+32+size)),
# rounded up to the next multiple of 32.
main.loader: main.sav
@echo "Generating main.loader from main.sav..."
@size_hex=$$(dd if=main.sav bs=1 skip=$$((0x1002C)) count=4 2>/dev/null | od -An -tx1 | tr -s ' ') && \
set -- $$size_hex && \
size=$$(( 0x$$1 + (0x$$2 << 8) + (0x$$3 << 16) + (0x$$4 << 24) )); \
header_extra=$$((0x002C + 4 + 32)) ; \
length=$$(( header_extra + size )); \
total=$$(( (length + 31) & ~31 )); \
dd if=main.sav bs=1 skip=$$((0x10000)) count=$$total of=main.loader 2>/dev/null
clean :
rm -f *.bin *.bmp *.elf *.gba *.o *.raw *.sa1 *.sav *.vpk *.loader

View File

@ -1,39 +0,0 @@
# DLC Loader
This loader scans cards with the **e-Reader** (or in Japan, the **e-Reader+**) and sends the contents to `LinkCard`.
## Required tools for compiling
- Windows
- _devkitARM_ with _GCC 14.1.0_
- `nedcmake.exe`, `nevpk.exe`, `nedcenc.exe`, `raw2bmp.exe`, `neflmake.exe`
* Cross-platform versions are available [here](https://github.com/AkBKukU/e-reader-dev) and [here](https://github.com/breadbored/nedclib)
- _Git Bash_ or some way to run Unix commands like `dd`
## Compile
``` bash
# verify tool paths in the `Makefile`!
make clean
make # USA region, English language
make JAP=1 # JAP region, Japanese language
make JAP=1 ENG=1 # JAP region, English language
# ^ check that NAME = ローダー and `Makefile` is encoded in Shift JIS
```
This will generate files named `main.bin.0*.bin` (the card parts) and also a `main.loader` file, which you can use to feed `LinkCard` as the _loader_.
## Encode cards
```bash
nedcmake -i testcard.txt -o testcard.bin -type 3 -bin -raw -fill 1 -region 1 -name "Game name"
# region 1 = USA
# region 2 = JAP
```
## Convert cards to BMP for distribution
```bash
raw2bmp -i testcard.bin-01.raw -o testcard.bin-01
# generates testcard.bin-01.bmp
```

View File

@ -1,60 +0,0 @@
/**********************************/
/* NINTENDO E-READER STARTUP CODE */
/**********************************/
/* Author : Tim Schuerewegen */
/* Version : 1.0 */
/**********************************/
.GLOBAL _start
.TEXT
.ARM
_start:
@ enter thumb mode
LDR R0, =(_start_thumb+1)
BX R0
@ For some reason the usa e-reader subtracts 0x0001610C from the value at
@ address 0x02000008 if it is not "valid". This is only the case when
@ running as dot code, not when running from flash. However, it is
@ recommended to put a "valid" value at that address because the jap
@ e-reader does not have this kind of "protection".
@ 0x02000000 <= valid value < 0x020000E4
.POOL
.THUMB
_start_thumb:
@ save return address
PUSH {LR}
@ clear bss section
_bss_clear:
LDR R0, =__bss_start
LDR R1, =__bss_end
MOV R2, #0
_bss_clear_loop:
CMP R0, R1
BEQ _bss_clear_exit
STRB R2, [R0]
ADD R0, #1
B _bss_clear_loop
_bss_clear_exit:
@ restore return address
POP {R3}
MOV LR, R3
@ jump to main
LDR R3, =main
BX R3
.ALIGN
.POOL
.END

View File

@ -1,43 +0,0 @@
/***********************************/
/* NINTENDO E-READER LINKER SCRIPT */
/***********************************/
/* Author : Tim Schuerewegen */
/* Version : 1.0 */
/***********************************/
OUTPUT_FORMAT( "elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH( arm)
ENTRY( _start)
SECTIONS
{
. = 0x02000000;
.text :
{
*(.text)
. = ALIGN(4);
} = 0xff
.rodata :
{
*(.rodata)
. = ALIGN(4);
} = 0xff
.data :
{
*(.data)
. = ALIGN(4);
} = 0xff
.bss :
{
__bss_start = .;
*(.bss)
. = ALIGN(4);
}
__bss_end = .;
__end = .;
}

View File

@ -1,32 +0,0 @@
const iconv = require('iconv-lite'); // "^0.6.3"
const data = {
"MSG_WAITING_GAME": "ゲームを待っています",
"MSG_SCAN_CARD": "カードをスキャンしてください",
"MSG_TRANSFERRING": "転送中",
"MSG_CARD_SENT": "カード送信済み",
"MSG_ERROR": "エラー",
"MSG_PRESS_B_CANCEL": "ビーを押してキャンセル"
};
/*
const data = {
"MSG_WAITING_GAME": "  ",
"MSG_SCAN_CARD": "   ",
"MSG_TRANSFERRING": "",
"MSG_CARD_SENT": " ",
"MSG_ERROR": "",
"MSG_PRESS_B_CANCEL": "   ",
"MSG_NUMBERS": ""
};
*/
for (const key in data) {
const str = data[key];
const buf = iconv.encode(str, 'Shift_JIS');
const hexBytes = Array.from(buf).map(byte => '0x' + byte.toString(16).padStart(2, '0'));
hexBytes.push("0x00");
console.log(`/* "${str}" */`);
console.log(`const u8 ${key}[] = { ${hexBytes.join(', ')} };`);
console.log('');
}

View File

@ -1,25 +0,0 @@
#ifndef DEF_H
#define DEF_H
#ifndef __cplusplus
typedef enum { false, true } bool;
#endif
typedef volatile unsigned char vu8;
typedef volatile unsigned short vu16;
typedef volatile unsigned int vu32;
typedef volatile unsigned long long vu64;
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
typedef signed char s8;
typedef signed short s16;
typedef signed int s32;
typedef signed long long s64;
typedef bool (*CancelCallback)(void);
#endif

View File

@ -1,160 +0,0 @@
#ifndef ERAPI_H
#define ERAPI_H
#include "def.h"
#define ERAPI_KEY_A 0x0001
#define ERAPI_KEY_B 0x0002
#define ERAPI_KEY_SELECT 0x0004
#define ERAPI_KEY_START 0x0008
#define ERAPI_KEY_RIGHT 0x0010
#define ERAPI_KEY_LEFT 0x0020
#define ERAPI_KEY_UP 0x0040
#define ERAPI_KEY_DOWN 0x0080
#define ERAPI_KEY_R 0x0100
#define ERAPI_KEY_L 0x0200
#define ERAPI_RAM_START 0x02000000
#define ERAPI_RAM_END 0x02027800
#define ERAPI_EXIT_RESTART 0
#define ERAPI_EXIT_TO_MENU 2
typedef u32 (*FUNC_U32_X1)(u32 r0);
typedef u32 (*FUNC_U32_X2)(u32 r0, u32 r1);
typedef u32 (*FUNC_U32_X3)(u32 r0, u32 r1, u32 r2);
typedef u32 (*FUNC_U32_X4)(u32 r0, u32 r1, u32 r2, u32 r3);
typedef u32 (*FUNC_U32_X5)(u32 r0, u32 r1, u32 r2, u32 r3, u32 r4);
typedef u32 (*FUNC_U32_X6)(u32 r0, u32 r1, u32 r2, u32 r3, u32 r4, u32 r5);
#define ERAPI_FUNC_X1 ((FUNC_U32_X1) * (vu32*)0x030075FC)
#define ERAPI_FUNC_X2 ((FUNC_U32_X2) * (vu32*)0x030075FC)
#define ERAPI_FUNC_X3 ((FUNC_U32_X3) * (vu32*)0x030075FC)
#define ERAPI_FUNC_X4 ((FUNC_U32_X4) * (vu32*)0x030075FC)
#define ERAPI_FUNC_X5 ((FUNC_U32_X5) * (vu32*)0x030075FC)
#define ERAPI_FUNC_X6 ((FUNC_U32_X6) * (vu32*)0x030075FC)
typedef u8 ERAPI_HANDLE_REGION;
typedef u16 ERAPI_HANDLE_SPRITE;
typedef struct _ERAPI_SPRITE ERAPI_SPRITE;
struct _ERAPI_SPRITE {
u8* data_gfx; // .... .... .... .... ....
u8* data_pal; // .... .... .... .... ....
u8 width; // 0x08 0x06 0x06 0x02 0x04
u8 height; // 0x08 0x06 0x02 0x02 0x04
u8 frames; // 0x04 0x04 0x02 0x02 0x04
u8 unk1; // 0x01 0x01 0x01 0x01 0x01
u8 unk2; // 0x08 0x00 0x00 0x00 0x00
u8 unk3; // 0x08 0x00 0x00 0x00 0x00
u8 unk4; // 0x03 0x04 0x02 0x02 0x04
};
typedef struct _ERAPI_BACKGROUND ERAPI_BACKGROUND;
struct _ERAPI_BACKGROUND {
u8* data_gfx;
u8* data_pal;
u8* data_map;
u16 tiles;
u16 palettes;
};
// #define ERAPI_STUB
#ifndef ERAPI_STUB
#define ERAPI_Div(a, b) ERAPI_FUNC_X3(0x103, a, b)
#define ERAPI_Mod(a, b) ERAPI_FUNC_X3(0x104, a, b)
#define ERAPI_PlaySoundSystem(a) ERAPI_FUNC_X2(0x105, a)
#define ERAPI_0106(a, b) ERAPI_FUNC_X3(0x106, a, b)
#define ERAPI_Rand() ERAPI_FUNC_X1(0x107)
#define ERAPI_SetSoundVolume(a, b) ERAPI_FUNC_X3(0x108, a, b)
#define ERAPI_0109(a, b, c) ERAPI_FUNC_X4(0x109, a, b, c)
#define ERAPI_010A(a, b) ERAPI_FUNC_X2(0x10A, a, b)
#define ERAPI_Set0400XXXX(a, b) ERAPI_FUNC_X3(0x10B, a, b)
#define ERAPI_Get0400XXXX(a) ERAPI_FUNC_X2(0x10C, a)
#define ERAPI_RandMax(a) ERAPI_FUNC_X2(0x112, a)
#define ERAPI_SetSoundSpeed(a, b) ERAPI_FUNC_X3(0x113, a, b)
#define ERAPI_SoundPause(a) ERAPI_FUNC_X2(0x116, a)
#define ERAPI_SoundResume(a) ERAPI_FUNC_X2(0x117, a)
#define ERAPI_PlaySoundSystemEx(a, b) ERAPI_FUNC_X3(0x118, a, b)
#define ERAPI_IsSoundPlaying(a, b) ERAPI_FUNC_X3(0x119, a, b)
#define ERAPI_GetExitCount() ERAPI_FUNC_X1(0x11D)
#define ERAPI_PlaySoundCustom(a, b) ERAPI_FUNC_X3(0x12F, (u32)a, b)
#define ERAPI_PlaySoundCustomEx(a, b, c) ERAPI_FUNC_X4(0x131, (u32)a, b, c)
#define ERAPI_FadeIn(a) ERAPI_FUNC_X2(0x200, a)
#define ERAPI_FadeOut(a) ERAPI_FUNC_X2(0x201, a)
#define ERAPI_LoadBackgroundSystem(a, b) ERAPI_FUNC_X3(0x210, b, a)
#define ERAPI_SetBackgroundOffset(a, b, c) ERAPI_FUNC_X4(0x211, a, b, c)
#define ERAPI_SetBackgroundAutoScroll(a, b, c) ERAPI_FUNC_X4(0x212, a, b, c)
#define ERAPI_SetBackgroundMirrorToggle(a, b) ERAPI_FUNC_X3(0x213, a, b)
#define ERAPI_SetBackgroundMode(a) ERAPI_FUNC_X2(0x219, a)
#define ERAPI_LayerShow(a) ERAPI_FUNC_X2(0x220, a)
#define ERAPI_LayerHide(a) ERAPI_FUNC_X2(0x221, a)
#define ERAPI_LoadBackgroundCustom(a, b) ERAPI_FUNC_X3(0x22D, a, (u32)b)
#define ERAPI_SpriteCreateSystem(a, b) ERAPI_FUNC_X3(0x230, a, b)
#define ERAPI_SpriteFree(a) ERAPI_FUNC_X2(0x231, a)
#define ERAPI_SetSpritePos(a, b, c) ERAPI_FUNC_X4(0x232, a, b, c)
#define ERAPI_SpriteFrameNext(a) ERAPI_FUNC_X2(0x234, a)
#define ERAPI_SpriteFramePrev(a) ERAPI_FUNC_X2(0x235, a)
#define ERAPI_SetSpriteFrame(a, b) ERAPI_FUNC_X3(0x236, a, b)
#define ERAPI_SetSpriteAutoMove(a, b, c) ERAPI_FUNC_X4(0x239, a, b, c)
#define ERAPI_SpriteAutoAnimate(a, b, c) ERAPI_FUNC_X4(0x23C, a, b, c)
#define ERAPI_SpriteAutoRotateUntilAngle(a, b, c) ERAPI_FUNC_X4(0x23E, a, b, c)
#define ERAPI_SpriteAutoRotateByAngle(a, b, c) ERAPI_FUNC_X4(0x23F, a, b, c)
#define ERAPI_SpriteAutoRotateByTime(a, b, c) ERAPI_FUNC_X4(0x240, a, b, c)
#define ERAPI_SetSpriteAutoMoveHorizontal(a, b) ERAPI_FUNC_X3(0x242, a, b)
#define ERAPI_SetSpriteAutoMoveVertical(a, b) ERAPI_FUNC_X3(0x243, a, b)
#define ERAPI_SpriteDrawOnBackground(a, b, c) ERAPI_FUNC_X4(0x245, a, b, c)
#define ERAPI_SpriteShow(a) ERAPI_FUNC_X2(0x246, a)
#define ERAPI_SpriteHide(a) ERAPI_FUNC_X2(0x247, a)
#define ERAPI_SpriteMirrorToggle(a, b) ERAPI_FUNC_X3(0x248, a, b)
#define ERAPI_GetSpritePos(a, b, c) ERAPI_FUNC_X4(0x24C, a, (u32)b, (u32)c)
#define ERAPI_SpriteCreateCustom(a, b) ERAPI_FUNC_X3(0x24D, a, (u32)b)
#define ERAPI_SpriteMove(a, b, c) ERAPI_FUNC_X4(0x257, a, b, c)
#define ERAPI_SpriteAutoScaleUntilSize(a, b, c) ERAPI_FUNC_X4(0x25B, a, b, c)
#define ERAPI_SpriteAutoScaleBySize(a, b, c) ERAPI_FUNC_X4(0x25C, a, b, c)
#define ERAPI_HANDLE_SpriteAutoScaleWidthUntilSize(a, b, c) \
ERAPI_FUNC_X4(0x25D, a, b, c)
#define ERAPI_SpriteAutoScaleHeightBySize(a, b, c) ERAPI_FUNC_X4(0x25E, a, b, c)
#define ERAPI_SetSpriteVisible(a, b) ERAPI_FUNC_X3(0x266, a, b)
#define ERAPI_SetBackgroundPalette(a, b, c) ERAPI_FUNC_X4(0x27E, (u32)a, b, c)
#define ERAPI_GetBackgroundPalette(a, b, c) ERAPI_FUNC_X4(0x27F, (u32)a, b, c)
#define ERAPI_SetSpritePalette(a, b, c) ERAPI_FUNC_X4(0x280, a, b, c)
#define ERAPI_GetSpritePalette(a, b, c) ERAPI_FUNC_X4(0x281, (u32)a, b, c)
#define ERAPI_ClearPalette() ERAPI_FUNC_X1(0x282)
#define ERAPI_CreateRegion(a, b, c, d, e, f) \
ERAPI_FUNC_X4(0x290, (a << 8) | b, (c << 8) | d, (e << 8) | f)
#define ERAPI_SetRegionColor(a, b) ERAPI_FUNC_X3(0x291, a, b)
#define ERAPI_ClearRegion(a) ERAPI_FUNC_X2(0x292, a)
#define ERAPI_SetPixel(a, b, c) ERAPI_FUNC_X3(0x293, a, (b << 8) | c)
#define ERAPI_GetPixel(a, b, c) ERAPI_FUNC_X3(0x294, a, (b << 8) | c)
#define ERAPI_DrawLine(a, b, c, d, e) \
ERAPI_FUNC_X4(0x295, a, (b << 8) | c, (d << 8) | e)
#define ERAPI_DrawRect(a, b, c, d, e, f) \
ERAPI_FUNC_X4(0x296, (a << 8) | f, (b << 8) | c, (d << 8) | e)
#define ERAPI_SetTextColor(a, b, c) ERAPI_FUNC_X3(0x298, a, (b << 8) | c)
#define ERAPI_DrawText(a, b, c, d) ERAPI_FUNC_X4(0x299, a, (b << 8) | c, (u32)d)
#define ERAPI_SetTextSize(a, b) ERAPI_FUNC_X2(0x29A, (a << 8) | b)
#define ERAPI_SetBackgroundModeRaw(a) ERAPI_FUNC_X2(0x29F, (u32)a)
#define ERAPI_02B5(a, b) ERAPI_FUNC_X3(0x2B5, a, b)
#define ERAPI_GetTextWidth(a, b) ERAPI_FUNC_X3(0x2C0, a, (u32)b)
#define ERAPI_GetTextWidthEx(a, b, c) ERAPI_FUNC_X3(0x2C1, (a << 8) | c, (u32)b)
#define ERAPI_02C2(a) ERAPI_FUNC_X2(0x2C2, (u32)a)
#define ERAPI_02C3(a) ERAPI_FUNC_X2(0x2C3, a)
#define ERAPI_02DD(a, b) ERAPI_FUNC_X3(0x2DD, a, b)
#define ERAPI_FlashWriteSectorSingle(a, b) ERAPI_FUNC_X3(0x2DE, a, (u32)b)
#define ERAPI_FlashReadSectorSingle(a, b) ERAPI_FUNC_X3(0x2DF, a, (u32)b)
#define ERAPI_SoftReset() ERAPI_FUNC_X1(0x2E0)
#define ERAPI_InitMemory(a) ERAPI_FUNC_X2(0x2EA, a)
#define ERAPI_FlashWriteSectorMulti(a, b, c) ERAPI_FUNC_X4(0x2ED, a, b, c)
#define ERAPI_FlashReadPart(a, b, c) ERAPI_FUNC_X4(0x2EE, a, (u32)b, c)
#define ERAPI_RandInit(a) ERAPI_FUNC_X2(0x2F1, a)
#define ERAPI_RenderFrame(a) ERAPI_FUNC_X2(0x300, a)
#define ERAPI_GetKeyStateSticky() ERAPI_FUNC_X1(0x301)
#define ERAPI_GetKeyStateRaw() ERAPI_FUNC_X1(0x302)
#define ERAPI_ScanDotCode(buffer) ERAPI_FUNC_X2(0x2C2, buffer)
#endif
#endif

View File

@ -1,118 +0,0 @@
#ifndef LINK_H
#define LINK_H
#include "def.h"
#define MEM_IO 0x04000000
#define REG_BASE MEM_IO
#define REG_RCNT *(vu16*)(REG_BASE + 0x0134)
#define REG_SIOCNT *(vu16*)(REG_BASE + 0x0128)
#define REG_SIOMLT_RECV *(vu16*)(REG_BASE + 0x0120)
#define REG_SIOMLT_SEND *(vu16*)(REG_BASE + 0x012A)
#define REG_SIOMULTI0 *(vu16*)(REG_BASE + 0x0120)
#define BITS_PLAYER_ID 4
#define BIT_READY 3
#define BIT_ERROR 6
#define BIT_START 7
#define BIT_MULTIPLAYER 13
#define BIT_IRQ 14
#define BIT_GENERAL_PURPOSE_HIGH 15
#define DISCONNECTED 0xFFFF
inline void setMultiPlayMode(int baudRate) {
REG_RCNT = 0;
REG_SIOCNT = (1 << BIT_MULTIPLAYER) | baudRate;
REG_SIOMLT_SEND = 0;
}
static void setGeneralPurposeMode() {
REG_RCNT = 1 << BIT_GENERAL_PURPOSE_HIGH;
REG_SIOCNT = 0;
}
inline void setData(u16 data) {
REG_SIOMLT_SEND = data;
}
inline u16 getDataFromPlayer0() {
return REG_SIOMULTI0;
}
inline void startTransfer() {
REG_SIOCNT |= 1 << BIT_START;
}
static void stopTransfer() {
REG_SIOCNT &= ~(1 << BIT_START);
}
inline bool isSending() {
return (REG_SIOCNT >> BIT_START) & 1;
}
inline bool wasTransferOk() {
bool allReady = (REG_SIOCNT >> BIT_READY) & 1;
bool hasError = (REG_SIOCNT >> BIT_ERROR) & 1;
return allReady && !hasError;
}
inline u32 assignedPlayerId() {
return (REG_SIOCNT & (0b11 << BITS_PLAYER_ID)) >> BITS_PLAYER_ID;
}
// ---
bool send(u16 data, CancelCallback cancel) {
setData(data);
startTransfer();
while (isSending()) {
if (cancel()) {
stopTransfer();
return false;
}
}
if (!wasTransferOk() || assignedPlayerId() != 1)
return false;
return true;
}
bool sendAndExpect(u16 data, u16 expect, CancelCallback cancel) {
u16 received;
do {
bool sent = false;
for (u32 attempt = 0; attempt < 3; attempt++) {
if (send(data, cancel)) {
sent = true;
break;
}
}
if (!sent)
return false;
received = getDataFromPlayer0();
bool isReset = received == 0 && expect != 0xFBFB;
if (isReset)
return false;
} while (received != expect);
return true;
}
u16 sendAndReceiveExcept(u16 data, u16 except, CancelCallback cancel) {
u16 received;
do {
if (!send(data, cancel))
return DISCONNECTED;
received = getDataFromPlayer0();
} while (received == except);
return received;
}
#endif

View File

@ -1,290 +0,0 @@
#include "def.h"
#include "erapi.h"
#include "link.h"
#include "protocol.h"
// Enable this to simulate failed scans with ⮜ and successful scans with ➤
#define DEBUG_MODE 0
// Enable this to display error codes
#define DISPLAY_ERROR_CODES 1
// Japanese strings are encoded as Shift-JIS byte arrays.
#ifdef REGION_JAP
#if DISPLAY_ERROR_CODES == 1
/* "" */
const u8 MSG_NUMBERS[] = {0x82, 0x4f, 0x82, 0x50, 0x82, 0x51, 0x82,
0x52, 0x82, 0x53, 0x82, 0x54, 0x82, 0x55,
0x82, 0x56, 0x82, 0x57, 0x82, 0x58, 0x00};
#endif
#ifdef LANGUAGE_ENG
const u8 MSG_WAITING_GAME[] = {
0x82, 0x76, 0x82, 0x60, 0x82, 0x68, 0x82, 0x73, 0x82, 0x68, 0x82,
0x6d, 0x82, 0x66, 0x81, 0x40, 0x82, 0x65, 0x82, 0x6e, 0x82, 0x71,
0x81, 0x40, 0x82, 0x66, 0x82, 0x60, 0x82, 0x6c, 0x82, 0x64, 0x00};
const u8 MSG_SCAN_CARD[] = {
0x82, 0x6f, 0x82, 0x6b, 0x82, 0x64, 0x82, 0x60, 0x82, 0x72, 0x82,
0x64, 0x81, 0x40, 0x82, 0x72, 0x82, 0x62, 0x82, 0x60, 0x82, 0x6d,
0x81, 0x40, 0x82, 0x78, 0x82, 0x6e, 0x82, 0x74, 0x82, 0x71, 0x81,
0x40, 0x82, 0x62, 0x82, 0x60, 0x82, 0x71, 0x82, 0x63, 0x00};
const u8 MSG_TRANSFERRING[] = {0x82, 0x73, 0x82, 0x71, 0x82, 0x60, 0x82,
0x6d, 0x82, 0x72, 0x82, 0x65, 0x82, 0x64,
0x82, 0x71, 0x82, 0x71, 0x82, 0x68, 0x82,
0x6d, 0x82, 0x66, 0x00};
const u8 MSG_CARD_SENT[] = {0x82, 0x62, 0x82, 0x60, 0x82, 0x71, 0x82,
0x63, 0x81, 0x40, 0x82, 0x72, 0x82, 0x64,
0x82, 0x6d, 0x82, 0x73, 0x00};
const u8 MSG_ERROR[] = {0x82, 0x64, 0x82, 0x71, 0x82, 0x71,
0x82, 0x6e, 0x82, 0x71, 0x00};
const u8 MSG_PRESS_B_CANCEL[] = {
0x82, 0x6f, 0x82, 0x71, 0x82, 0x64, 0x82, 0x72, 0x82, 0x72, 0x81, 0x40,
0x82, 0x61, 0x81, 0x40, 0x82, 0x73, 0x82, 0x6e, 0x81, 0x40, 0x82, 0x62,
0x82, 0x60, 0x82, 0x6d, 0x82, 0x62, 0x82, 0x64, 0x82, 0x6b, 0x00};
#else
const u8 MSG_WAITING_GAME[] = {0x83, 0x51, 0x81, 0x5b, 0x83, 0x80, 0x82,
0xf0, 0x91, 0xd2, 0x82, 0xc1, 0x82, 0xc4,
0x82, 0xa2, 0x82, 0xdc, 0x82, 0xb7, 0x00};
const u8 MSG_SCAN_CARD[] = {0x83, 0x4a, 0x81, 0x5b, 0x83, 0x68, 0x82, 0xf0,
0x83, 0x58, 0x83, 0x4c, 0x83, 0x83, 0x83, 0x93,
0x82, 0xb5, 0x82, 0xc4, 0x82, 0xad, 0x82, 0xbe,
0x82, 0xb3, 0x82, 0xa2, 0x00};
const u8 MSG_TRANSFERRING[] = {0x93, 0x5d, 0x91, 0x97, 0x92, 0x86, 0x00};
const u8 MSG_CARD_SENT[] = {0x83, 0x4a, 0x81, 0x5b, 0x83, 0x68, 0x91, 0x97,
0x90, 0x4d, 0x8d, 0xcf, 0x82, 0xdd, 0x00};
const u8 MSG_ERROR[] = {0x83, 0x47, 0x83, 0x89, 0x81, 0x5b, 0x00};
const u8 MSG_PRESS_B_CANCEL[] = {0x83, 0x72, 0x81, 0x5b, 0x82, 0xf0, 0x89, 0x9f,
0x82, 0xb5, 0x82, 0xc4, 0x83, 0x4c, 0x83, 0x83,
0x83, 0x93, 0x83, 0x5a, 0x83, 0x8b, 0x00};
#endif
#else
const char* MSG_WAITING_GAME = "Waiting for game...";
const char* MSG_SCAN_CARD = "Scan a card!";
const char* MSG_TRANSFERRING = "Transferring...";
const char* MSG_CARD_SENT = "Card sent!";
const char* MSG_ERROR = "Error!";
const char* MSG_PRESS_B_CANCEL = "Press B to cancel";
#endif
#define CARD_BUFFER_SIZE 2100
#define SCAN_SUCCESS 6
#define POST_TRANSFER_WAIT 60
extern int __end[];
ERAPI_HANDLE_REGION region;
u8 card[CARD_BUFFER_SIZE];
const u16 palette[] = {0x0000, 0xFFFF};
u32 previousKeys = 0;
#if DISPLAY_ERROR_CODES == 1
void codeToString(char* buf, int num) {
#ifdef REGION_JAP
const u8* digits = MSG_NUMBERS;
int temp[5];
int len = 0;
do {
temp[len++] = num % 10;
num /= 10;
} while (num && len < 5);
u8* p = (u8*)buf;
for (int i = len - 1; i >= 0; --i) {
int idx = temp[i] * 2;
*p++ = digits[idx];
*p++ = digits[idx + 1];
}
*p = 0;
#else
char temp[6];
int pos = 0;
do {
temp[pos++] = '0' + (num % 10);
num /= 10;
} while (num && pos < 5);
int j = 0;
while (pos)
buf[j++] = temp[--pos];
buf[j] = '\0';
#endif
}
#endif
void print(const char* text, bool canCancel);
bool cancel();
void reset();
int main() {
// init
ERAPI_FadeIn(1);
ERAPI_InitMemory((ERAPI_RAM_END - (u32)__end) >> 10);
ERAPI_SetBackgroundMode(0);
// palette
ERAPI_SetBackgroundPalette(&palette[0], 0x00, 0x02);
// region & text
region = ERAPI_CreateRegion(0, 0, 1, 1, 28, 10);
ERAPI_SetTextColor(region, 0x01, 0x00);
// background
ERAPI_LoadBackgroundSystem(3, 20);
// loop
while (1) {
u32 errorCode = 0;
// init loader
reset();
// "Waiting for game..."
print(MSG_WAITING_GAME, false);
// handshake with game
if (!sendAndExpect(HANDSHAKE_1, HANDSHAKE_1, cancel))
continue;
if (!sendAndExpect(HANDSHAKE_2, HANDSHAKE_2, cancel))
continue;
if (!sendAndExpect(HANDSHAKE_3, HANDSHAKE_3, cancel))
continue;
// wait for card request
u16 cardRequest = sendAndReceiveExcept(HANDSHAKE_3, HANDSHAKE_3, cancel);
if (cardRequest != GAME_REQUEST) {
errorCode = 1;
goto error;
}
// confirm card request
if (!sendAndExpect(GAME_ANIMATING, EREADER_ANIMATING, cancel))
continue;
if (!send(EREADER_ANIMATING, cancel))
continue;
// scan card
if (!sendAndExpect(EREADER_READY, GAME_READY, cancel)) {
errorCode = 2;
goto error;
}
// "Scan a card!"
print(MSG_SCAN_CARD, false);
#if DEBUG_MODE == 1
u32 resultCode = 0;
while (true) {
u32 debugKeys = ERAPI_GetKeyStateRaw();
if ((debugKeys & ERAPI_KEY_LEFT) != 0) {
resultCode = SCAN_SUCCESS - 1;
break;
}
if ((debugKeys & ERAPI_KEY_RIGHT) != 0) {
resultCode = SCAN_SUCCESS;
const char msg[] = "HelloWorld";
const u32 msgLen = sizeof(msg) - 1;
const u32 byteCount = CARD_BUFFER_SIZE - CARD_OFFSET;
for (u32 i = 0; i < byteCount; i++)
card[CARD_OFFSET + i] = i == byteCount - 1 ? '!' : msg[i % msgLen];
break;
}
}
#else
u32 resultCode = ERAPI_ScanDotCode((u32)card);
#endif
if (resultCode != SCAN_SUCCESS) {
errorCode = 3;
goto error;
}
// "Transferring..."
print(MSG_TRANSFERRING, true);
// transfer start
if (!sendAndExpect(EREADER_SEND_READY, GAME_RECEIVE_READY, cancel)) {
errorCode = 4;
goto error;
}
if (!send(EREADER_SEND_START, cancel)) {
errorCode = 5;
goto error;
}
// transfer
u32 checksum = 0;
for (u32 o = CARD_OFFSET; o < CARD_SIZE; o += 2) {
u16 block = *(u16*)(card + o);
if (!send(block, cancel)) {
errorCode = 6;
goto error;
}
checksum += block;
}
if (!send(checksum & 0xffff, cancel)) {
errorCode = 7;
goto error;
}
if (!send(checksum >> 16, cancel)) {
errorCode = 8;
goto error;
}
if (!send(EREADER_SEND_END, cancel)) {
errorCode = 9;
goto error;
}
// "Card sent!"
print(MSG_CARD_SENT, false);
for (u32 i = 0; i < POST_TRANSFER_WAIT; i++)
ERAPI_RenderFrame(1);
continue;
error:
// "Error!"
ERAPI_ClearRegion(region);
ERAPI_DrawText(region, 0, 0, MSG_ERROR);
#if DISPLAY_ERROR_CODES == 1
char errorCodeStr[11];
codeToString(errorCodeStr, errorCode);
ERAPI_DrawText(region, 0, 16, errorCodeStr);
#else
ERAPI_DrawText(region, 0, 16, MSG_WAITING_GAME);
#endif
ERAPI_RenderFrame(1);
send(EREADER_CANCEL, cancel);
send(EREADER_CANCEL, cancel);
send(EREADER_ANIMATING, cancel);
send(EREADER_SIO_END, cancel);
}
setGeneralPurposeMode();
// exit
return ERAPI_EXIT_TO_MENU;
}
void print(const char* text, bool canCancel) {
ERAPI_ClearRegion(region);
ERAPI_DrawText(region, 0, 0, text);
if (canCancel)
ERAPI_DrawText(region, 0, 16, MSG_PRESS_B_CANCEL);
ERAPI_RenderFrame(1);
}
bool cancel() {
u32 keys = ERAPI_GetKeyStateRaw();
bool isPressed =
(previousKeys & ERAPI_KEY_B) == 0 && (keys & ERAPI_KEY_B) != 0;
previousKeys = keys;
return isPressed;
}
void reset() {
setGeneralPurposeMode();
setMultiPlayMode(3); // 3 = 115200 bps
for (u32 i = 0; i < CARD_BUFFER_SIZE; i++)
((vu8*)card)[i] = 0;
}

View File

@ -1,43 +0,0 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H
#include "def.h"
// This protocol is only meant to be used by homebrew games using
// gba-link-connection's LinkCard library and its loader. It's highly inspired
// by `4-e`, but not compatible with it since SMA4 cards start from offset
// `0x72`, while cards generated with `nedcmake v1.4` start from offset `0x4e`.
// Both card formats are valid, though. That said, you could make it compatible
// by changing `CARD_OFFSET` and `CARD_SIZE` to the commented values, or by just
// recompiling `4-e` to skip 0x4e bytes (no pun intended) instead of 0x72.
#define CARD_OFFSET /*0x72*/ 0x4e
#define CARD_SIZE /*2112*/ 2076
#define HANDSHAKE_1 0xfbfb
#define HANDSHAKE_2 0x5841
#define HANDSHAKE_3 0x4534
#define GAME_REQUEST 0xecec
#define GAME_ANIMATING 0xf3f3
#define EREADER_ANIMATING 0xf2f2
#define EREADER_READY 0xf1f1
#define GAME_READY 0xefef
#define EREADER_CANCEL 0xf7f7
#define EREADER_SEND_READY 0xf9f9
#define GAME_RECEIVE_READY 0xfefe
#define EREADER_SEND_START 0xfdfd
#define EREADER_SEND_END 0xfcfc
#define GAME_RECEIVE_OK 0xf5f5
#define GAME_RECEIVE_ERROR 0xf4f4
#define EREADER_SIO_END 0xf3f3
#define GAME_SIO_END 0xf1f1
#endif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1 +0,0 @@
Lorem ipsum odor amet, consectetuer adipiscing elit. Venenatis sociosqu gravida non ridiculus curabitur ligula. Metus eleifend porttitor commodo ligula dapibus. Vitae praesent consectetur ante eget luctus elementum luctus. Gravida elementum inceptos sem adipiscing etiam vulputate vestibulum. Facilisis pulvinar ornare consectetur, erat nec semper interdum lacus aenean. Magnis ut donec habitant sodales laoreet et diam. Accumsan lacinia imperdiet suscipit; venenatis porttitor magna. Vel ligula enim dictum nisl ligula pharetra vestibulum. Eget dis molestie donec ligula felis hendrerit nam! Blandit venenatis elit fringilla purus quis volutpat habitant iaculis. Non tempor facilisis ultricies bibendum euismod. In curae vehicula sapien praesent commodo quam ligula proin dignissim. Mollis faucibus dignissim mauris aliquam risus finibus risus. Bibendum penatibus laoreet montes urna libero senectus diam. Donec faucibus condimentum curae justo cursus aliquam rhoncus. Est est potenti; commodo litora elementum taciti. Sed integer nisl varius libero torquent vitae odio? Fringilla sollicitudin sed himenaeos sit ex. Torquent habitant penatibus sed eleifend nibh maximus at? Elementum ut porttitor mauris turpis egestas mollis nulla. Lacus cubilia imperdiet massa erat libero venenatis bibendum. Natoque primis mus adipiscing mauris, consectetur urna sed. Libero phasellus volutpat neque suscipit tellus turpis. Dapibus molestie justo rhoncus phasellus nibh fusce fermentum. Sodales porta taciti sociosqu velit suspendisse est class placerat. Scelerisque malesuada etiam placerat fusce tortor neque laoreet elementum. Justo nullam dignissim feugiat hendrerit porttitor. Enim molestie facilisis turpis nec volutpat nascetur orci sollicitudin! Penatibus habitasse lacus justo commodo, aliquet aptent suscipit amet. Nunc blandit scelerisque egestas, praesent tristique sed. Egestas sit at erat tellus, curabitur nec dui sociosqu. Fringilla enim cursus elit habitant aenean tempus porttitor urna. Posue

View File

@ -1,293 +0,0 @@
#
# 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 ../../lib/iwram_code ../_lib/libgbfs
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
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
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) package
package: $(BUILD)
cd gbfs_files/ && gbfs loader.gbfs * && mv loader.gbfs .. && cd ..
../gbfs.sh "$(TARGET).gba" loader.gbfs "$(TARGET).out.gba"
start: package
start "$(TARGET).out.gba"
restart: rebuild start
# EOF

View File

@ -1,128 +0,0 @@
// (0) Include the header
#include "../../../lib/LinkCard.hpp"
#include "../../_lib/common.h"
#include "../../_lib/interrupt.h"
extern "C" {
#include "../../_lib/libgbfs/gbfs.h"
}
#define USA_LOADER "usa.loader"
#define JAP_LOADER_ENGLISH "japeng.loader"
#define JAP_LOADER_JAPANESE "jap.loader"
static const GBFS_FILE* fs = find_first_gbfs_file(0);
// (1) Create a LinkCard instance
LinkCard* linkCard = new LinkCard();
void init() {
Common::initTTE();
interrupt_init();
interrupt_add(INTR_VBLANK, []() {});
}
int main() {
init();
// Ensure there are GBFS files
if (fs == NULL) {
Common::log("! GBFS file not found");
while (true)
;
} else if (gbfs_get_obj(fs, USA_LOADER, NULL) == NULL) {
Common::log("! usa.loader not found (GBFS)");
while (true)
;
} else if (gbfs_get_obj(fs, JAP_LOADER_ENGLISH, NULL) == NULL) {
Common::log("! japeng.loader not found (GBFS)");
while (true)
;
}
bool a = true;
while (true) {
std::string output = "LinkCard_demo (v8.0.3)\n\n";
output += "Device: ";
// (2) Probe the connected device
u32 initialVCount = REG_VCOUNT;
auto device = linkCard->getConnectedDevice([&initialVCount]() {
u32 elapsed = (REG_VCOUNT - initialVCount + 228) % 228;
return elapsed > 150;
});
switch (device) {
case LinkCard::ConnectedDevice::E_READER_JAP:
case LinkCard::ConnectedDevice::E_READER_USA: {
output += "e-Reader\n\nPress A to send the loader.";
if (Common::didPress(KEY_A, a)) {
Common::log("Sending...\n\nPress B to cancel");
auto fileName = device == LinkCard::ConnectedDevice::E_READER_JAP
? JAP_LOADER_ENGLISH
: USA_LOADER;
u32 loaderSize;
const u8* loader = (const u8*)gbfs_get_obj(fs, fileName, &loaderSize);
// (3) Send the DLC loader program
auto result = linkCard->sendLoader(loader, loaderSize, []() {
u16 keys = ~REG_KEYS & KEY_ANY;
return keys & KEY_B;
});
if (result == LinkCard::SendResult::SUCCESS)
Common::log("Success! Press DOWN");
else
Common::log("Error " + std::to_string((int)result) +
"! Press DOWN");
Common::waitForKey(KEY_DOWN);
}
break;
}
case LinkCard::ConnectedDevice::DLC_LOADER: {
output += "DLC Loader\n\nPress A to receive a card.";
if (Common::didPress(KEY_A, a)) {
Common::log("Receiving...\n\nPress B to cancel");
// (4) Receive scanned cards
u8 card[LINK_CARD_SIZE + 1]; // +1 to interpret contents as a string
auto result = linkCard->receiveCard(card, []() {
u16 keys = ~REG_KEYS & KEY_ANY;
return keys & KEY_B;
});
if (result == LinkCard::ReceiveResult::SUCCESS) {
card[LINK_CARD_SIZE] = '\0';
Common::log("Success! Press DOWN\n\n" + std::string((char*)card));
} else
Common::log("Error " + std::to_string((int)result) +
"! Press DOWN");
Common::waitForKey(KEY_DOWN);
}
break;
}
default: {
output +=
"??\n\n- Grab a GBA Link Cable\n- Use the 1P end here\n- Use "
"the 2P end to e-Reader\n- Boot e-Reader in another GBA\n- Go to "
"\"Communication\"\n- Choose \"To Game Boy Advance\"\n- Press A";
break;
}
}
VBlankIntrWait();
Common::log(output);
}
return 0;
}

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -1,21 +1,30 @@
// (0) Include the header
#include "../../../lib/LinkCube.hpp"
#include "../../_lib/common.h"
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
bool a = true, b = true, l = true;
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() {
Common::initTTE();
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_add(INTR_VBLANK, []() {});
interrupt_add(INTR_SERIAL, LINK_CUBE_ISR_SERIAL);
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() {
@ -32,7 +41,7 @@ int main() {
while (true) {
// Title
std::string output =
"LinkCube_demo (v8.0.3)" + std::string(reset ? " !RESET!" : "") +
"LinkCube_demo (v7.0.1)" + std::string(reset ? " !RESET!" : "") +
"\n\nPress A to send\nPress B to clear\n (L = "
"+1024)\n\nLast sent: " +
std::to_string(counter) +
@ -40,13 +49,13 @@ int main() {
")\n\nReceived:\n" + received;
// (4) Send 32-bit values
if (Common::didPress(KEY_A, a)) {
if (didPress(KEY_A, a)) {
counter++;
linkCube->send(counter);
}
// +1024
if (Common::didPress(KEY_L, l)) {
if (didPress(KEY_L, l)) {
counter += 1024;
linkCube->send(counter);
}
@ -57,7 +66,7 @@ int main() {
}
// Clear
if (Common::didPress(KEY_B, b))
if (didPress(KEY_B, b))
received = "";
// Reset warning
@ -74,8 +83,38 @@ int main() {
// Print
VBlankIntrWait();
Common::log(output);
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;
}

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -1,26 +1,22 @@
// (0) Include the header
#include "../../../lib/LinkGPIO.hpp"
#include "../../_lib/common.h"
#include "../../_lib/interrupt.h"
#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);
std::string value(std::string name, LinkGPIO::Pin pin, bool isHigh);
// (1) Create a LinkGPIO instance
LinkGPIO* linkGPIO = new LinkGPIO();
static vu32 irqCount = 0;
void SERIAL() {
irqCount++;
}
void init() {
Common::initTTE();
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
interrupt_init();
interrupt_add(INTR_VBLANK, []() {});
interrupt_add(INTR_SERIAL, SERIAL);
irq_init(NULL);
irq_add(II_VBLANK, NULL);
// (2) Initialize the library
linkGPIO->reset();
@ -29,95 +25,83 @@ void init() {
int main() {
init();
bool left = true, up = true, right = true, down = true;
bool start = true, select = true;
while (true) {
std::string output = "LinkGPIO_demo (v8.0.3)\n\n";
// (3) Use the pins
std::string output = "LinkGPIO_demo (v7.0.1)\n\n";
// Commands
u16 keys = ~REG_KEYS & KEY_ANY;
bool setSOOutput = keys & KEY_L;
bool setSDOutput = keys & KEY_UP;
bool setSCOutput = keys & KEY_R;
bool setSOInput = keys & KEY_LEFT;
bool setSDInput = keys & KEY_DOWN;
bool setSCInput = keys & KEY_RIGHT;
bool sendSOHigh = keys & KEY_B;
bool sendSDHigh = keys & KEY_START;
bool sendSCHigh = keys & KEY_A;
// (3/4) Set modes
if (Common::didPress(KEY_LEFT, left))
linkGPIO->setMode(
LinkGPIO::Pin::SI,
(LinkGPIO::Direction)((int)linkGPIO->getMode(LinkGPIO::Pin::SI) + 1));
if (Common::didPress(KEY_UP, up))
linkGPIO->setMode(
LinkGPIO::Pin::SD,
(LinkGPIO::Direction)((int)linkGPIO->getMode(LinkGPIO::Pin::SD) + 1));
if (Common::didPress(KEY_DOWN, down))
linkGPIO->setMode(
LinkGPIO::Pin::SC,
(LinkGPIO::Direction)((int)linkGPIO->getMode(LinkGPIO::Pin::SC) + 1));
if (Common::didPress(KEY_RIGHT, right))
linkGPIO->setMode(
LinkGPIO::Pin::SO,
(LinkGPIO::Direction)((int)linkGPIO->getMode(LinkGPIO::Pin::SO) + 1));
// (3) Write pins
if (linkGPIO->getMode(LinkGPIO::Pin::SI) == LinkGPIO::Direction::OUTPUT)
linkGPIO->writePin(LinkGPIO::Pin::SI, keys & KEY_L);
if (linkGPIO->getMode(LinkGPIO::Pin::SO) == LinkGPIO::Direction::OUTPUT)
linkGPIO->writePin(LinkGPIO::Pin::SO, keys & KEY_R);
if (linkGPIO->getMode(LinkGPIO::Pin::SD) == LinkGPIO::Direction::OUTPUT)
linkGPIO->writePin(LinkGPIO::Pin::SD, keys & KEY_B);
if (linkGPIO->getMode(LinkGPIO::Pin::SC) == LinkGPIO::Direction::OUTPUT)
linkGPIO->writePin(LinkGPIO::Pin::SC, keys & KEY_A);
// (5) Subscribe to SI falling
if (Common::didPress(KEY_START, start))
linkGPIO->setSIInterrupts(!linkGPIO->getSIInterrupts());
if (Common::didPress(KEY_SELECT, select))
irqCount = 0;
// Print modes
// Modes
output += mode("SI", LinkGPIO::Pin::SI);
output += mode("SO", LinkGPIO::Pin::SO);
output += mode("SD", LinkGPIO::Pin::SD);
output += mode("SC", LinkGPIO::Pin::SC);
// Print separator
// Separator
output += "\n---\n\n";
// Print values
output += value("SI", LinkGPIO::Pin::SI);
output += value("SO", LinkGPIO::Pin::SO);
output += value("SD", LinkGPIO::Pin::SD);
output += value("SC", LinkGPIO::Pin::SC);
// Values
output += value("SI", LinkGPIO::Pin::SI, false);
output += value("SO", LinkGPIO::Pin::SO, sendSOHigh);
output += value("SD", LinkGPIO::Pin::SD, sendSDHigh);
output += value("SC", LinkGPIO::Pin::SC, sendSCHigh);
// Print interrupts
if (linkGPIO->getMode(LinkGPIO::Pin::SI) == LinkGPIO::Direction::INPUT)
output +=
"\nSI IRQ: " + std::to_string(linkGPIO->getSIInterrupts()) +
(irqCount > 0
? " !!!" + (irqCount > 1 ? " (x" + std::to_string(irqCount) + ")"
: "")
: "");
// Set modes
if (setSOOutput)
linkGPIO->setMode(LinkGPIO::Pin::SO, LinkGPIO::Direction::OUTPUT);
if (setSDOutput)
linkGPIO->setMode(LinkGPIO::Pin::SD, LinkGPIO::Direction::OUTPUT);
if (setSCOutput)
linkGPIO->setMode(LinkGPIO::Pin::SC, LinkGPIO::Direction::OUTPUT);
if (setSOInput)
linkGPIO->setMode(LinkGPIO::Pin::SO, LinkGPIO::Direction::INPUT);
if (setSDInput)
linkGPIO->setMode(LinkGPIO::Pin::SD, LinkGPIO::Direction::INPUT);
if (setSCInput)
linkGPIO->setMode(LinkGPIO::Pin::SC, LinkGPIO::Direction::INPUT);
output +=
"\n\n---\nUse the D-PAD to change modes\nUse the buttons to set "
"values\nUse STA/SEL to toggle SI IRQ";
// Set values
if (linkGPIO->getMode(LinkGPIO::Pin::SO) == LinkGPIO::Direction::OUTPUT)
linkGPIO->writePin(LinkGPIO::Pin::SO, sendSOHigh);
if (linkGPIO->getMode(LinkGPIO::Pin::SD) == LinkGPIO::Direction::OUTPUT)
linkGPIO->writePin(LinkGPIO::Pin::SD, sendSDHigh);
if (linkGPIO->getMode(LinkGPIO::Pin::SC) == LinkGPIO::Direction::OUTPUT)
linkGPIO->writePin(LinkGPIO::Pin::SC, sendSCHigh);
// Print
VBlankIntrWait();
Common::log(output);
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}
std::string mode(std::string name, LinkGPIO::Pin pin) {
return name + ": " +
(linkGPIO->getMode(pin) == LinkGPIO::Direction::OUTPUT ? "OUTPUT\n"
: "INPUT\n");
}
std::string value(std::string name, LinkGPIO::Pin pin) {
std::string value(std::string name, LinkGPIO::Pin pin, bool isHigh) {
auto title = name + ": ";
// (4) Read pins
return (linkGPIO->getMode(pin) == LinkGPIO::Direction::INPUT ? "< " : "> ") +
title + std::to_string(linkGPIO->readPin(pin)) + "\n";
return linkGPIO->getMode(pin) == LinkGPIO::Direction::INPUT
? "< " + title + std::to_string(linkGPIO->readPin(pin)) + "\n"
: "> " + title + std::to_string(isHigh) + "\n";
}

View File

@ -1,289 +0,0 @@
#
# 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 ../../lib/iwram_code
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
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
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

View File

@ -1,173 +0,0 @@
// (0) Include the header
#include "../../../lib/LinkIR.hpp"
#include "../../_lib/common.h"
#include "../../_lib/interrupt.h"
void sendNECSignal();
void receiveNECSignal();
void sendGeneric38kHzSignal();
void receiveGeneric38kHzSignal();
// (1) Create a LinkIR instance
LinkIR* linkIR = new LinkIR();
bool isConnected = false;
void init() {
Common::initTTE();
// (2) Add the required interrupt service routines
interrupt_init();
interrupt_add(INTR_VBLANK, []() {});
interrupt_add(INTR_SERIAL, LINK_IR_ISR_SERIAL);
interrupt_add(INTR_TIMER2, []() {});
interrupt_add(INTR_TIMER3, []() {});
// (3) Initialize the library
isConnected = linkIR->activate();
}
int main() {
init();
bool a = true, b = true, left = true, right = true;
while (true) {
std::string output = "LinkIR_demo (v8.0.3)\n\n";
output += std::string("IR adapter: ") +
(isConnected ? "DETECTED" : "not detected");
output += "\n\nA = Send NEC signal";
output += "\nB = Receive NEC signal";
output += "\n\nRIGHT = Send 38kHz signal";
output += "\nLEFT = Receive 38kHz / copy";
if (Common::didPress(KEY_A, a))
sendNECSignal();
if (Common::didPress(KEY_B, b))
receiveNECSignal();
if (Common::didPress(KEY_RIGHT, right))
sendGeneric38kHzSignal();
if (Common::didPress(KEY_LEFT, left))
receiveGeneric38kHzSignal();
VBlankIntrWait();
Common::log(output);
}
return 0;
}
void sendNECSignal() {
// (4) Send NEC signals
Common::log("Sending...");
// Addr=0x04, Cmd=0x08
linkIR->sendNEC(0x04, 0x08);
Common::log("Sent!\n\nPress DOWN");
Common::waitForKey(KEY_DOWN);
}
void receiveNECSignal() {
// (5) Receive NEC signals
Common::log("Receiving...");
u8 address, command;
if (linkIR->receiveNEC(address, command, 1000000)) {
Common::log("NEC signal detected!\n\nAddress: " + std::to_string(address) +
"\nCommand: " + std::to_string(command) + "\n\nPress DOWN");
} else {
Common::log("No NEC signal detected!\n\nPress DOWN");
}
Common::waitForKey(KEY_DOWN);
}
void sendGeneric38kHzSignal() {
// (6) Send 38kHz signals
Common::log("Sending...");
// Example with NEC signal Addr=0x04, Cmd=0x03
linkIR->send((u16[]){
// leader
9000, 4500,
// address 0x04, LSB first (bits: 0,0,1,0,0,0,0,0)
560, 560, // bit0: 0
560, 560, // bit1: 0
560, 1690, // bit2: 1
560, 560, // bit3: 0
560, 560, // bit4: 0
560, 560, // bit5: 0
560, 560, // bit6: 0
560, 560, // bit7: 0
// inverted address 0xFB, LSB first (bits: 1,1,0,1,1,1,1,1)
560, 1690, // bit0: 1
560, 1690, // bit1: 1
560, 560, // bit2: 0
560, 1690, // bit3: 1
560, 1690, // bit4: 1
560, 1690, // bit5: 1
560, 1690, // bit6: 1
560, 1690, // bit7: 1
// command 0x03, LSB first (bits: 1,1,0,0,0,0,0,0)
560, 1690, // bit0: 1
560, 1690, // bit1: 1
560, 560, // bit2: 0
560, 560, // bit3: 0
560, 560, // bit4: 0
560, 560, // bit5: 0
560, 560, // bit6: 0
560, 560, // bit7: 0
// inverted command 0xFC, LSB first (bits: 0,0,1,1,1,1,1,1)
560, 560, // bit0: 0
560, 560, // bit1: 0
560, 1690, // bit2: 1
560, 1690, // bit3: 1
560, 1690, // bit4: 1
560, 1690, // bit5: 1
560, 1690, // bit6: 1
560, 1690, // bit7: 1
// final burst
560,
// terminator
0});
Common::log("Sent!\n\nPress DOWN");
Common::waitForKey(KEY_DOWN);
}
void receiveGeneric38kHzSignal() {
// (7) Receive 38kHz signals
Common::log("Receiving...");
u16 pulses[3000] = {};
bool didReceive = false;
if (linkIR->receive(pulses, 3000, 1000000)) {
didReceive = true;
std::string received;
u32 i = 0;
for (i = 0; pulses[i] != 0; i++) {
if (i > 0)
received += ", ";
received += std::to_string(pulses[i]);
}
u8 address, command;
bool isNEC = linkIR->parseNEC(pulses, address, command);
received = "Press START to resend (NEC=" + std::to_string(isNEC) + ")\n" +
std::to_string(i) + " // " + received;
Common::log(received);
} else {
Common::log("No signal detected!\n\nPress START");
}
Common::waitForKey(KEY_START);
if (didReceive) {
linkIR->send(pulses);
Common::log("Sent!\n\nPress DOWN");
Common::waitForKey(KEY_DOWN);
}
}

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -5,6 +5,7 @@
#include "main.h"
#include <tonc.h>
#include <functional>
#include "../../_lib/interrupt.h"
@ -24,9 +25,9 @@ std::string outgoingData = "";
u32 counter = 0;
u32 frameCounter = 0;
bool left = true, right = true, up = true, down = true;
bool a = true, b = true, l = true, r = true;
bool start = true, select = true;
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 = "";
@ -34,14 +35,15 @@ std::string selectedDomain = "";
LinkMobile* linkMobile = nullptr;
void init() {
Common::initTTE();
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
}
int main() {
init();
start:
log("LinkMobile_demo (v8.0.3)\n\nPress A to start");
log("LinkMobile_demo (v7.0.1)\n\nPress A to start");
waitForA();
// (1) Create a LinkMobile instance
@ -49,9 +51,12 @@ start:
// (2) Add the required interrupt service routines
interrupt_init();
interrupt_add(INTR_VBLANK, LINK_MOBILE_ISR_VBLANK);
interrupt_add(INTR_SERIAL, LINK_MOBILE_ISR_SERIAL);
interrupt_add(INTR_TIMER3, LINK_MOBILE_ISR_TIMER);
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();
@ -72,7 +77,7 @@ start:
}
// SELECT = stop
if (Common::didPress(KEY_SELECT, select)) {
if (didPress(KEY_SELECT, select)) {
bool didShutdown = linkMobile->getState() == LinkMobile::State::SHUTDOWN;
if (hasError || didShutdown) {
@ -85,7 +90,7 @@ start:
if (!didShutdown) {
log("Waiting...");
Link::wait(228 * 60 * 3);
wait(228 * 60 * 3);
}
goto start;
@ -98,13 +103,13 @@ start:
switch (linkMobile->getState()) {
case LinkMobile::State::SESSION_ACTIVE: {
// L = Read Configuration
if (Common::didPress(KEY_L, l)) {
if (didPress(KEY_L, l)) {
readConfiguration();
waitForA();
}
// R = Call someone
if (Common::didPress(KEY_R, r)) {
if (didPress(KEY_R, r)) {
std::string number = getNumberInput();
if (number != "") {
// (4) Call someone
@ -113,7 +118,7 @@ start:
}
// START = Call the ISP
if (Common::didPress(KEY_START, start)) {
if (didPress(KEY_START, start)) {
std::string password = getPasswordInput();
if (password != "") {
// (7) Connect to the internet
@ -124,7 +129,7 @@ start:
}
case LinkMobile::State::CALL_ESTABLISHED: {
// L = hang up
if (Common::didPress(KEY_L, l)) {
if (didPress(KEY_L, l)) {
// (6) Hang up
linkMobile->hangUp();
}
@ -132,7 +137,7 @@ start:
}
case LinkMobile::State::PPP_ACTIVE: {
// A = DNS query
if (Common::didPress(KEY_A, a) && !waitingDNS) {
if (didPress(KEY_A, a) && !waitingDNS) {
std::string domain = getDomainInput();
if (domain != "") {
// (8) Run DNS queries
@ -141,7 +146,7 @@ start:
}
}
// L = hang up
if (Common::didPress(KEY_L, l)) {
if (didPress(KEY_L, l)) {
// (6) Hang up
linkMobile->hangUp();
}
@ -165,16 +170,10 @@ void handleP2P() {
outgoingData = linkMobile->getRole() == LinkMobile::Role::CALLER
? "caller!!!"
: "receiver!!!";
transfer(dataTransfer, outgoingData, 0xFF, true);
transfer(dataTransfer, outgoingData, 0xff, true);
}
if (dataTransfer.completed) {
if (!dataTransfer.success) {
// Hang up when a transfer fails
linkMobile->hangUp();
return;
}
// Save a copy of last received data
if (dataTransfer.size > 0)
lastCompletedTransfer = dataTransfer;
@ -194,7 +193,7 @@ void handleP2P() {
if (frameCounter >= TRANSFER_FREQUENCY) {
// Transfer every N frames
frameCounter = 0;
transfer(dataTransfer, outgoingData, 0xFF, true);
transfer(dataTransfer, outgoingData, 0xff, true);
}
if (lastCompletedTransfer.completed) {
@ -244,7 +243,7 @@ void handlePPP() {
log("Downloading... (" + std::to_string(chunk) + ", " +
std::to_string(retry) + ")\n (hold START = close conn)\n\n" + output);
if (Common::didPress(KEY_START, start)) {
if (didPress(KEY_START, start)) {
log("Closing...");
LinkMobile::CloseConn closeConn;
linkMobile->closeConnection(
@ -419,43 +418,43 @@ std::string getInput(std::string& field,
std::string output = "Type " + inputName + ":\n\n";
output += ">> " + field + "\n\n";
if (Common::didPress(KEY_RIGHT, right)) {
if (didPress(KEY_RIGHT, right)) {
selectedX++;
if (selectedX >= (int)renderRows[0].size())
selectedX = renderRows[0].size() - 1;
}
if (Common::didPress(KEY_LEFT, left)) {
if (didPress(KEY_LEFT, left)) {
selectedX--;
if (selectedX < 0)
selectedX = 0;
}
if (Common::didPress(KEY_UP, up)) {
if (didPress(KEY_UP, up)) {
selectedY--;
if (selectedY < 0)
selectedY = 0;
}
if (Common::didPress(KEY_DOWN, down)) {
if (didPress(KEY_DOWN, down)) {
selectedY++;
if (selectedY >= (int)renderRows.size())
selectedY = renderRows.size() - 1;
}
if (Common::didPress(KEY_B, b)) {
if (didPress(KEY_B, b)) {
if (field.size() > 0)
field = field.substr(0, field.size() - 1);
else
return "";
}
if (Common::didPress(KEY_A, a)) {
if (didPress(KEY_A, a)) {
if (field.size() < maxChars)
field += renderRows[selectedY][selectedX];
}
if (Common::didPress(KEY_SELECT, select)) {
if (didPress(KEY_SELECT, select)) {
field = defaultValues[selectedDefaultValue].value;
selectedDefaultValue = (selectedDefaultValue + 1) % defaultValues.size();
}
if (Common::didPress(KEY_START, start))
if (didPress(KEY_START, start))
return field;
if (altName != "" && Common::didPress(KEY_L, l))
if (altName != "" && didPress(KEY_L, l))
altActive = !altActive;
for (int y = 0; y < (int)renderRows.size(); y++) {
@ -584,7 +583,9 @@ std::string getResultString(LinkMobile::CommandResult cmdResult) {
void log(std::string text) {
if (linkMobile != nullptr)
VBlankIntrWait();
Common::log(text);
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}
std::string toStr(char* chars, int size) {
@ -595,8 +596,32 @@ std::string toStr(char* chars, int size) {
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 (!Common::didPress(KEY_A, a))
while (!didPress(KEY_A, a))
;
}
@ -605,6 +630,6 @@ template <typename I>
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];
rc[i] = digits[(w >> j) & 0x0f];
return rc;
}

View File

@ -3,7 +3,8 @@
#include "../../../lib/LinkMobile.hpp"
#include "../../_lib/common.h"
#include <string>
#include <vector>
struct DefaultValue {
std::string name;
@ -25,11 +26,11 @@ std::string getNumberInput();
std::string getPasswordInput();
std::string getDomainInput();
std::string getTextInput(std::string& field,
u32 maxChars,
unsigned int maxChars,
std::string inputName,
std::vector<DefaultValue> defaultValues);
std::string getInput(std::string& field,
u32 maxChars,
unsigned int maxChars,
std::string inputName,
std::vector<std::vector<std::string>> rows,
std::vector<std::vector<std::string>> altRows,
@ -43,6 +44,8 @@ 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>

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -1,15 +1,18 @@
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
// (0) Include the header
#include "../../../lib/LinkPS2Keyboard.hpp"
#include "../../_lib/common.h"
#include "../../_lib/interrupt.h"
void log(std::string text);
static std::string scanCodes = "";
static std::string output = "";
static vu32 irqCount = 0;
static u32 irqs = 0;
inline void VBLANK() {}
void SERIAL() {
LINK_PS2_KEYBOARD_ISR_SERIAL();
irqCount++;
irqs++;
}
// (1) Create a LinkPS2Keyboard instance
@ -19,19 +22,22 @@ LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 scanCode) {
});
void init() {
Common::initTTE();
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_add(INTR_VBLANK, []() {});
interrupt_add(INTR_SERIAL, SERIAL);
interrupt_set_handler(INTR_VBLANK, VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_SERIAL, SERIAL);
interrupt_enable(INTR_SERIAL);
}
int main() {
init();
while (true) {
std::string output = "LinkPS2Keyboard_demo (v8.0.3)\n\n";
std::string output = "LinkPS2Keyboard_demo (v7.0.1)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkPS2Keyboard->isActive()) {
@ -41,7 +47,7 @@ int main() {
if (keys & KEY_A) {
// (3) Initialize the library
Common::log("Waiting...");
log("Waiting...");
linkPS2Keyboard->activate();
VBlankIntrWait();
continue;
@ -49,16 +55,22 @@ int main() {
} else {
if (keys & KEY_B) {
scanCodes = "";
irqCount = 0;
irqs = 0;
}
output += std::to_string(irqCount) + " - " + scanCodes;
output += std::to_string(irqs) + " - " + scanCodes;
}
// Print
VBlankIntrWait();
LINK_PS2_KEYBOARD_ISR_VBLANK();
Common::log(output);
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -1,30 +1,43 @@
// (0) Include the header
#include "../../../lib/LinkPS2Mouse.hpp"
#include "../../_lib/common.h"
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
void log(std::string text);
inline void VBLANK() {}
inline void TIMER() {}
// (1) Create a LinkPS2Mouse instance
LinkPS2Mouse* linkPS2Mouse = new LinkPS2Mouse(2);
inline void KEYPAD() {
SoftReset();
}
void init() {
Common::initTTE();
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_add(INTR_VBLANK, []() {});
interrupt_add(INTR_TIMER2, []() {});
interrupt_set_handler(INTR_VBLANK, VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_TIMER2, TIMER);
interrupt_enable(INTR_TIMER2);
// B = SoftReset
// Interrupt to handle B event (to reset)
REG_KEYCNT = 0b10 | (1 << 14);
interrupt_add(INTR_KEYPAD, Common::ISR_reset);
interrupt_set_handler(INTR_KEYPAD, KEYPAD);
interrupt_enable(INTR_KEYPAD);
}
int main() {
init();
while (true) {
std::string output = "LinkPS2Mouse_demo (v8.0.3)\n\n";
std::string output = "LinkPS2Mouse_demo (v7.0.1)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkPS2Mouse->isActive()) {
@ -34,7 +47,7 @@ int main() {
if (keys & KEY_A) {
// (3) Initialize the library
Common::log("Waiting...");
log("Waiting...");
linkPS2Mouse->activate();
VBlankIntrWait();
continue;
@ -49,8 +62,14 @@ int main() {
// Print
VBlankIntrWait();
Common::log(output);
log(output);
}
return 0;
}
void log(std::string text) {
tte_erase_screen();
tte_write("#{P:0,0}");
tte_write(text.c_str());
}

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -1,19 +1,27 @@
// (0) Include the header
#include "../../../lib/LinkRawCable.hpp"
#include "../../_lib/common.h"
#include <tonc.h>
#include <string>
#include "../../_lib/interrupt.h"
void log(std::string text);
void wait(u32 verticalLines);
inline void VBLANK() {}
// (1) Create a LinkRawCable instance
LinkRawCable* linkRawCable = new LinkRawCable();
void init() {
Common::initTTE();
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_add(INTR_VBLANK, []() {});
interrupt_add(INTR_SERIAL, LINK_RAW_CABLE_ISR_SERIAL);
interrupt_set_handler(INTR_VBLANK, VBLANK);
interrupt_enable(INTR_VBLANK);
interrupt_set_handler(INTR_SERIAL, LINK_RAW_CABLE_ISR_SERIAL);
interrupt_enable(INTR_SERIAL);
}
int main() {
@ -25,7 +33,7 @@ int main() {
u16 prevKeys = 0;
while (true) {
std::string output = "LinkRawCable_demo (v8.0.3)\n\n";
std::string output = "LinkRawCable_demo (v7.0.1)\n\n";
u16 keys = ~REG_KEYS & KEY_ANY;
if (!linkRawCable->isActive()) {
@ -47,7 +55,7 @@ int main() {
output +=
"isReady() = " + std::to_string(linkRawCable->isReady()) + "\n\n";
if (firstTransfer) {
Common::log(output + "Waiting...");
log(output + "Waiting...");
firstTransfer = false;
}
@ -55,20 +63,20 @@ int main() {
// (4)/(5) Exchange 16-bit data with the connected consoles
if (prevKeys == 0 && keys != 0 && linkRawCable->isReady()) {
counter++;
Common::log(output + "...");
log(output + "...");
LinkRawCable::Response response =
linkRawCable->transfer(counter, []() {
u16 keys = ~REG_KEYS & KEY_ANY;
return (keys & KEY_L) && (keys & KEY_R);
});
Common::log(output + ">> " + std::to_string(counter));
Link::wait(228 * 60);
Common::log(output + "<< [" + std::to_string(response.data[0]) + "," +
std::to_string(response.data[1]) + "," +
std::to_string(response.data[2]) + "," +
std::to_string(response.data[3]) + "]\n" +
"_pID: " + std::to_string(response.playerId));
Link::wait(228 * 60);
log(output + ">> " + std::to_string(counter));
wait(228 * 60);
log(output + "<< [" + std::to_string(response.data[0]) + "," +
std::to_string(response.data[1]) + "," +
std::to_string(response.data[2]) + "," +
std::to_string(response.data[3]) + "]\n" +
"_pID: " + std::to_string(response.playerId));
wait(228 * 60);
}
} else {
// (6) Exchange data asynchronously
@ -76,17 +84,17 @@ int main() {
linkRawCable->getAsyncState() == LinkRawCable::AsyncState::IDLE) {
counter++;
linkRawCable->transferAsync(counter);
Common::log(output + ">> " + std::to_string(counter));
Link::wait(228 * 60);
log(output + ">> " + std::to_string(counter));
wait(228 * 60);
}
if (linkRawCable->getAsyncState() == LinkRawCable::AsyncState::READY) {
LinkRawCable::Response response = linkRawCable->getAsyncData();
Common::log(output + "<< [" + std::to_string(response.data[0]) + "," +
std::to_string(response.data[1]) + "," +
std::to_string(response.data[2]) + "," +
std::to_string(response.data[3]) + "]\n" +
"_pID: " + std::to_string(response.playerId));
Link::wait(228 * 60);
log(output + "<< [" + std::to_string(response.data[0]) + "," +
std::to_string(response.data[1]) + "," +
std::to_string(response.data[2]) + "," +
std::to_string(response.data[3]) + "]\n" +
"_pID: " + std::to_string(response.playerId));
wait(228 * 60);
}
}
@ -100,9 +108,27 @@ int main() {
// Print
VBlankIntrWait();
Common::log(output);
log(output);
prevKeys = keys;
}
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;
}
};
}

View File

@ -123,8 +123,9 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba -lgba-sprite-engine
BUILD := build
SRCDIRS := src ../_lib ../_lib/libgba-sprite-engine ../../lib ../../lib/iwram_code \
src/scenes
SRCDIRS := src ../_lib ../../lib \
src/scenes \
src/utils
DATADIRS :=
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
@ -165,12 +166,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -1,11 +1,10 @@
#define LINK_RAW_WIRELESS_ENABLE_LOGGING
#include "../../../lib/LinkRawWireless.hpp"
#include <libgba-sprite-engine/gba_engine.h>
#include "../../_lib/common.h"
#include <tonc.h>
#include "../../_lib/interrupt.h"
#include "scenes/DebugScene.h"
#include "utils/SceneUtils.h"
void setUpInterrupts();
void printTutorial();
@ -28,15 +27,19 @@ int main() {
return 0;
}
inline void ISR_reset() {
RegisterRamReset(RESET_REG | RESET_VRAM);
SoftReset();
}
inline void setUpInterrupts() {
interrupt_init();
interrupt_add(INTR_VBLANK, [] {});
interrupt_add(INTR_SERIAL, LINK_RAW_WIRELESS_ISR_SERIAL);
interrupt_set_handler(INTR_VBLANK, [] {});
interrupt_enable(INTR_VBLANK);
// A+B+START+SELECT = SoftReset
#if MULTIBOOT_BUILD == 0
// A+B+START+SELECT
REG_KEYCNT = 0b1100000000001111;
interrupt_add(INTR_KEYPAD, Common::ISR_reset);
#endif
interrupt_set_handler(INTR_KEYPAD, ISR_reset);
interrupt_enable(INTR_KEYPAD);
}

View File

@ -1,14 +1,14 @@
#define LINK_RAW_WIRELESS_ENABLE_LOGGING
#include "../../../../lib/LinkRawWireless.hpp"
#include "DebugScene.h"
#include <libgba-sprite-engine/background/text_stream.h>
#include <tonc.h>
#include <algorithm>
#include <functional>
#include "../../../_lib/common.h"
#include "../../../_lib/libgba-sprite-engine/scene.h"
#include "utils/InputHandler.h"
#include "utils/SceneUtils.h"
DebugScene::DebugScene(std::shared_ptr<GBAEngine> engine) : Scene(engine) {}
@ -28,8 +28,6 @@ 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::unique_ptr<InputHandler> rightHandler =
std::unique_ptr<InputHandler>(new InputHandler());
static std::vector<std::string> logLines;
static u32 currentLogLine = 0;
@ -114,8 +112,12 @@ void log(std::string string) {
scrollPageDown();
}
const u32* toArray(std::vector<u32> vector) {
return vector.data();
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> toArray(
std::vector<u32> vector) {
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> array;
for (u32 i = 0; i < vector.size(); i++)
array[i] = vector[i];
return array;
}
std::vector<Background*> DebugScene::backgrounds() {
@ -137,11 +139,10 @@ void DebugScene::load() {
};
log("---");
log("LinkRawWireless_demo");
log(" (v8.0.3)");
log("LinkRawWireless demo");
log(" (v7.0.1)");
log("");
log("START: reset wireless adapter");
log("RIGHT: restore from multiboot");
log("A: send command");
log("B: toggle log level");
log("UP/DOWN: scroll up/down");
@ -213,19 +214,19 @@ void DebugScene::addCommandMenuOptions() {
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x19 (StartHost)", .command = 0x19});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x1A (PollConnections)", .command = 0x1A});
CommandMenuOption{.name = "0x1a (AcceptConnections)", .command = 0x1a});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x1B (EndHost)", .command = 0x1B});
CommandMenuOption{.name = "0x1b (EndHost)", .command = 0x1b});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x1C (BroadcastReadStart)", .command = 0x1C});
CommandMenuOption{.name = "0x1c (BroadcastRead1)", .command = 0x1c});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x1D (BroadcastReadPoll)", .command = 0x1D});
CommandMenuOption{.name = "0x1d (BroadcastRead2)", .command = 0x1d});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x1E (BroadcastReadEnd)", .command = 0x1E});
CommandMenuOption{.name = "0x1e (BroadcastRead3)", .command = 0x1e});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x1F (Connect)", .command = 0x1F});
commandMenuOptions.push_back(CommandMenuOption{
.name = "0x20 (IsConnectionComplete)", .command = 0x20});
CommandMenuOption{.name = "0x1f (Connect)", .command = 0x1f});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x20 (IsFinishedConnect)", .command = 0x20});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x21 (FinishConnection)", .command = 0x21});
commandMenuOptions.push_back(
@ -253,7 +254,7 @@ void DebugScene::addCommandMenuOptions() {
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x39 (?)", .command = 0x39});
commandMenuOptions.push_back(
CommandMenuOption{.name = "0x3D (Bye)", .command = 0x3D});
CommandMenuOption{.name = "0x3d (Bye)", .command = 0x3d});
}
void DebugScene::processKeys(u16 keys) {
@ -265,7 +266,6 @@ void DebugScene::processKeys(u16 keys) {
rHandler->setIsPressed(keys & KEY_R);
selectHandler->setIsPressed(keys & KEY_SELECT);
startHandler->setIsPressed(keys & KEY_START);
rightHandler->setIsPressed(keys & KEY_RIGHT);
}
void DebugScene::processButtons() {
@ -314,9 +314,6 @@ void DebugScene::processButtons() {
if (startHandler->hasBeenPressedNow())
resetAdapter();
if (rightHandler->hasBeenPressedNow())
restoreExistingConnection();
}
void DebugScene::toggleLogLevel() {
@ -429,9 +426,9 @@ again2:
if (byte3 == -1)
goto again2;
u16 numberLow = Link::buildU16((u8)byte1, (u8)byte0);
u16 numberHigh = Link::buildU16((u8)byte3, (u8)byte2);
u32 number = Link::buildU32(numberHigh, numberLow);
u16 numberLow = linkRawWireless->buildU16((u8)byte1, (u8)byte0);
u16 numberHigh = linkRawWireless->buildU16((u8)byte3, (u8)byte2);
u32 number = linkRawWireless->buildU32(numberHigh, numberLow);
if (selectOption(">> 0x" + linkRawWireless->toHex(number, 8) + "?",
std::vector<std::string>{"yes", "no"}) == 1)
goto again0;
@ -448,7 +445,7 @@ again:
if (msB == -1)
goto again;
u16 number = Link::buildU16((u8)msB, (u8)lsB);
u16 number = linkRawWireless->buildU16((u8)msB, (u8)lsB);
if (selectOption(">> 0x" + linkRawWireless->toHex(number, 4) + "?",
std::vector<std::string>{"yes", "no"}) == 1)
goto again;
@ -474,37 +471,9 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
switch (command) {
case 0x10:
case 0x11:
return logOperation("sending " + name, []() {
LinkRawWireless::SignalLevelResponse response;
bool success = linkRawWireless->getSignalLevel(response);
if (success) {
log("< [levelH] " + std::to_string(response.signalLevels[0]));
log("< [levelC0] " + std::to_string(response.signalLevels[1]));
log("< [levelC1] " + std::to_string(response.signalLevels[2]));
log("< [levelC2] " + std::to_string(response.signalLevels[3]));
log("< [levelC3] " + std::to_string(response.signalLevels[4]));
}
return success;
});
case 0x12:
case 0x13:
goto simple;
case 0x13: {
return logOperation("sending " + name, []() {
LinkRawWireless::SystemStatusResponse response;
bool success = linkRawWireless->getSystemStatus(response);
if (success) {
log("< [device id] " + linkRawWireless->toHex(response.deviceId, 4));
log("< [player id] " + std::to_string(response.currentPlayerId));
log("< [state] " + std::to_string((int)response.adapterState));
log("< [closed] " + std::to_string(response.isServerClosed));
}
return success;
});
}
case 0x14: {
return logOperation("sending " + name, []() {
LinkRawWireless::SlotStatusResponse response;
@ -582,10 +551,10 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
return logOperation("sending " + name,
[]() { return linkRawWireless->startHost(); });
}
case 0x1A: {
case 0x1a: {
return logOperation("sending " + name, []() {
LinkRawWireless::PollConnectionsResponse response;
bool success = linkRawWireless->pollConnections(response);
LinkRawWireless::AcceptConnectionsResponse response;
bool success = linkRawWireless->acceptConnections(response);
if (success) {
for (u32 i = 0; i < response.connectedClientsSize; i++) {
@ -600,9 +569,9 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
return success;
});
}
case 0x1B: {
case 0x1b: {
return logOperation("sending " + name, []() {
LinkRawWireless::PollConnectionsResponse response;
LinkRawWireless::AcceptConnectionsResponse response;
bool success = linkRawWireless->endHost(response);
if (success) {
@ -618,17 +587,17 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
return success;
});
}
case 0x1C: {
case 0x1c: {
return logOperation("sending " + name, []() {
bool success = linkRawWireless->broadcastReadStart();
if (success)
log("NOW CALL 0x1D!");
log("NOW CALL 0x1d!");
return success;
});
}
case 0x1D: {
case 0x1d: {
return logOperation("sending " + name, [this]() {
LinkRawWireless::BroadcastReadPollResponse response;
bool success = linkRawWireless->broadcastReadPoll(response);
@ -651,25 +620,25 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
}
if (response.serversSize > 0)
log("NOW CALL 0x1E!");
log("NOW CALL 0x1e!");
else
log("No rooms? NOW CALL 0x1E!");
log("No rooms? NOW CALL 0x1e!");
}
return success;
});
}
case 0x1E: {
case 0x1e: {
return logOperation("sending " + name, []() {
bool success = linkRawWireless->broadcastReadEnd();
if (success)
log("NOW CALL 0x1F!");
log("NOW CALL 0x1f!");
return success;
});
}
case 0x1F: {
case 0x1f: {
u16 serverId = selectServerId();
if (serverId == -1)
return;
@ -691,13 +660,9 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
bool success = linkRawWireless->keepConnecting(response);
if (success) {
log(std::string("< [phase] ") +
(response.phase ==
LinkRawWireless::ConnectionPhase::STILL_CONNECTING
? "CONNECTING"
: response.phase == LinkRawWireless::ConnectionPhase::ERROR
? "ERROR"
: "SUCCESS"));
log(std::string("< [phase] ") + (response.phase == 0 ? "CONNECTING"
: response.phase == 1 ? "ERROR"
: "SUCCESS"));
if (response.phase == LinkRawWireless::ConnectionPhase::SUCCESS)
log("< [slot] " + std::to_string(response.assignedClientNumber));
@ -726,56 +691,24 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
auto data = selectDataToSend();
if (data.empty())
return;
u32 bytes = data[0];
data.erase(data.begin());
if (selectOption("What mode?",
std::vector<std::string>{"sync", "async"}) == 1) {
return logOperation("sending " + name, [&data]() {
u32 bytes = data[0];
data[0] = linkRawWireless->getSendDataHeaderFor(bytes);
return logOperation("sending " + name, [&data, bytes]() {
LinkRawWireless::RemoteCommand remoteCommand;
bool success = linkRawWireless->sendDataAndWait(
toArray(data), data.size(), remoteCommand, bytes);
bool success = linkRawWireless->sendCommandAsync(0x25, toArray(data),
data.size(), true);
if (!success) {
log("! not now");
return false;
if (success) {
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(remoteCommand.params[i]));
}
}
while (linkRawWireless->getAsyncState() ==
LinkRawWireless::AsyncState::WORKING)
;
auto result = linkRawWireless->getAsyncCommandResult();
if (result.success) {
log("< [notif] " + linkRawWireless->toHex(result.commandId));
for (u32 i = 0; i < result.dataSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(result.data[i]));
}
}
return success;
});
} else {
u32 bytes = data[0];
data.erase(data.begin());
return logOperation("sending " + name, [&data, bytes]() {
LinkRawWireless::CommandResult remoteCommand;
bool success = linkRawWireless->sendDataAndWait(
toArray(data), data.size(), remoteCommand, bytes);
if (success) {
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
for (u32 i = 0; i < remoteCommand.dataSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(remoteCommand.data[i]));
}
}
return success;
});
}
return success;
});
}
case 0x26: {
return logOperation("sending " + name, []() {
@ -799,39 +732,21 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
}
case 0x27: {
return logOperation("sending " + name, []() {
LinkRawWireless::CommandResult remoteCommand;
LinkRawWireless::RemoteCommand remoteCommand;
bool success = linkRawWireless->wait(remoteCommand);
if (success) {
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
for (u32 i = 0; i < remoteCommand.dataSize; i++) {
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(remoteCommand.data[i]));
linkRawWireless->toHex(remoteCommand.params[i]));
}
}
return success;
});
}
case 0x30: {
bool client0 = selectOption("Disconnect client 0?",
std::vector<std::string>{"yes", "no"}) == 0;
bool client1 = selectOption("Disconnect client 1?",
std::vector<std::string>{"yes", "no"}) == 0;
bool client2 = selectOption("Disconnect client 2?",
std::vector<std::string>{"yes", "no"}) == 0;
bool client3 = selectOption("Disconnect client 3?",
std::vector<std::string>{"yes", "no"}) == 0;
return logOperation("sending " + name,
[client0, client1, client2, client3]() {
bool success = linkRawWireless->disconnectClient(
client0, client1, client2, client3);
if (success)
log("DISCONNECTED!");
return success;
});
}
case 0x30:
case 0x32:
case 0x33:
case 0x34:
@ -842,7 +757,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
case 0x38:
case 0x39:
goto generic;
case 0x3D:
case 0x3d:
goto simple;
default:
return;
@ -895,13 +810,13 @@ int DebugScene::selectGameId() {
"GameID?",
std::vector<std::string>{"0x7FFF", "0x1234", "<random>", "<pick>"})) {
case 0: {
return 0x7FFF;
return 0x7fff;
}
case 1: {
return 0x1234;
}
case 2: {
return Link::buildU16(qran_range(0, 256), qran_range(0, 256));
return linkRawWireless->buildU16(qran_range(0, 256), qran_range(0, 256));
}
default: {
return selectU16();
@ -933,10 +848,6 @@ std::vector<u32> DebugScene::selectDataToSend() {
data.push_back(bytes);
if (selectOption(">> Include data?", std::vector<std::string>{"yes", "no"}) ==
1)
return data;
u32 words = (bytes + 3) / 4;
for (u32 i = 0; i < (u32)words; i++) {
int word = selectU32("Word " + std::to_string(i + 1) + "/" +
@ -987,9 +898,9 @@ void DebugScene::logGenericWaitCommand(std::string name, u32 id) {
return logOperation("sending " + name, [id, &data]() {
auto result =
linkRawWireless->sendCommand(id, toArray(data), data.size(), true);
for (u32 i = 0; i < result.dataSize; i++) {
for (u32 i = 0; i < result.responsesSize; i++) {
log("< [response" + std::to_string(i) + "] " +
linkRawWireless->toHex(result.data[i]));
linkRawWireless->toHex(result.responses[i]));
}
if (!result.success)
@ -997,14 +908,14 @@ void DebugScene::logGenericWaitCommand(std::string name, u32 id) {
log("Now WAITING...");
LinkRawWireless::CommandResult remoteCommand =
LinkRawWireless::RemoteCommand remoteCommand =
linkRawWireless->receiveCommandFromAdapter();
if (remoteCommand.success) {
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
for (u32 i = 0; i < remoteCommand.dataSize; i++) {
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
log("< [param" + std::to_string(i) + "] " +
linkRawWireless->toHex(remoteCommand.data[i]));
linkRawWireless->toHex(remoteCommand.params[i]));
}
}
@ -1023,9 +934,9 @@ void DebugScene::logSimpleCommand(std::string name,
logOperation("sending " + name, [id, &params]() {
auto result =
linkRawWireless->sendCommand(id, toArray(params), params.size());
for (u32 i = 0; i < result.dataSize; i++) {
for (u32 i = 0; i < result.responsesSize; i++) {
log("< [response" + std::to_string(i) + "] " +
linkRawWireless->toHex(result.data[i]));
linkRawWireless->toHex(result.responses[i]));
}
return result.success;
});
@ -1043,8 +954,3 @@ void DebugScene::resetAdapter() {
logOperation("resetting adapter",
[]() { return linkRawWireless->activate(); });
}
void DebugScene::restoreExistingConnection() {
logOperation("restoring from multiboot",
[]() { return linkRawWireless->restoreExistingConnection(); });
}

View File

@ -51,7 +51,6 @@ class DebugScene : public Scene {
void logSimpleCommand(std::string name, u32 id, std::vector<u32> params = {});
void logOperation(std::string name, std::function<bool()> operation);
void resetAdapter();
void restoreExistingConnection();
};
#endif // DEBUG_SCENE_H

View File

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

View File

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

View File

@ -134,7 +134,7 @@ TITLE := $(PROJ)
LIBS := -ltonc -lugba
BUILD := build
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
SRCDIRS := src ../_lib ../../lib
DATADIRS := data
INCDIRS := src
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
@ -176,12 +176,8 @@ LDFLAGS := $(ARCH) -Wl,--print-memory-usage,-Map,$(PROJ).map
# --- Multiboot ? ---
ifeq ($(strip $(bMB)), 1)
TARGET := $(PROJ).mb
CFLAGS += -DMULTIBOOT_BUILD=1
CXXFLAGS += -DMULTIBOOT_BUILD=1
else
TARGET := $(PROJ)
CFLAGS += -DMULTIBOOT_BUILD=0
CXXFLAGS += -DMULTIBOOT_BUILD=0
endif
# --- Save temporary files ? ---

View File

@ -13,10 +13,10 @@ void setup() {
}
uint32_t transfer(uint32_t value) {
uint8_t a = (value >> 24) & 0xFF;
uint8_t b = (value >> 16) & 0xFF;
uint8_t c = (value >> 8) & 0xFF;
uint8_t d = (value >> 0) & 0xFF;
uint8_t a = (value >> 24) & 0xff;
uint8_t b = (value >> 16) & 0xff;
uint8_t c = (value >> 8) & 0xff;
uint8_t d = (value >> 0) & 0xff;
a = SPI.transfer(a);
b = SPI.transfer(b);
@ -28,7 +28,7 @@ uint32_t transfer(uint32_t value) {
}
void loop() {
uint32_t received = transfer(0xAABBCCDD); // (2864434397)
uint32_t received = transfer(0xaabbccdd); // (2864434397)
Serial.println(received);

Some files were not shown because too many files have changed in this diff Show More