Compare commits
366 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01770d7398 | ||
|
|
f71706cc33 | ||
|
|
7e8aca5daa | ||
|
|
a7babd4ec2 | ||
|
|
983411ea68 | ||
|
|
6e5646e96b | ||
|
|
9447cdfff3 | ||
|
|
a9359ade1e | ||
|
|
6b94479c95 | ||
|
|
a8ab944b58 | ||
|
|
3d4b5f934d | ||
|
|
d8908ca004 | ||
|
|
1ac74f4cf1 | ||
|
|
0ebadc401f | ||
|
|
9af6dde347 | ||
|
|
5e212a0a81 | ||
|
|
25831307a4 | ||
|
|
6edef9ab20 | ||
|
|
5b20e96321 | ||
|
|
8fe9c5f710 | ||
|
|
9fc2d8af06 | ||
|
|
1534677446 | ||
|
|
e553ad67f0 | ||
|
|
a37e6fd517 | ||
|
|
9e05647244 | ||
|
|
2336bfeb2d | ||
|
|
ab590d90e5 | ||
|
|
8d20770e2c | ||
|
|
4a91763451 | ||
|
|
bf386778e6 | ||
|
|
dcbdf37250 | ||
|
|
6868ca18cd | ||
|
|
d7c81f65c3 | ||
|
|
ee70be4cf7 | ||
|
|
350000da5f | ||
|
|
8b8daf9152 | ||
|
|
dc5fa75dc1 | ||
|
|
729a77866a | ||
|
|
ea515ab1d4 | ||
|
|
a10d4c6f65 | ||
|
|
b620818178 | ||
|
|
3a19e21a15 | ||
|
|
4a8c769feb | ||
|
|
6e93e8650f | ||
|
|
f2cbf9906d | ||
|
|
25238c4ef4 | ||
|
|
948bbef979 | ||
|
|
7b6071116a | ||
|
|
15f8ada24e | ||
|
|
4f4631754e | ||
|
|
918d279eee | ||
|
|
9d4b61157a | ||
|
|
6f1908c648 | ||
|
|
15ce27ef33 | ||
|
|
78e6fab8f2 | ||
|
|
18f9afecce | ||
|
|
3645ca5d9f | ||
|
|
237472ee15 | ||
|
|
6b44e02278 | ||
|
|
5a97399b8e | ||
|
|
406ae0031f | ||
|
|
5cb96b5b69 | ||
|
|
ec14b25b37 | ||
|
|
c6ca9c563b | ||
|
|
25fff2de3a | ||
|
|
d95368060e | ||
|
|
4b79f8d7f5 | ||
|
|
afedb826ff | ||
|
|
fd39aaf65e | ||
|
|
115fb2ade2 | ||
|
|
bce0671881 | ||
|
|
5c0cf89008 | ||
|
|
86eed10480 | ||
|
|
97028c5231 | ||
|
|
b4393f03c0 | ||
|
|
ee50d4f731 | ||
|
|
3d0fa72083 | ||
|
|
5372106db5 | ||
|
|
724d21e75d | ||
|
|
bb2d417f3f | ||
|
|
37a8ad487b | ||
|
|
38f40894f7 | ||
|
|
0845b39136 | ||
|
|
39e80d0f88 | ||
|
|
de7554bf45 | ||
|
|
0086f66ad3 | ||
|
|
799e1aee00 | ||
|
|
219c14ef07 | ||
|
|
534378032a | ||
|
|
8b1de89179 | ||
|
|
4f664002ba | ||
|
|
5b5846aade | ||
|
|
8abb3d7ae4 | ||
|
|
c4d9c4a2c5 | ||
|
|
1becbcfbb8 | ||
|
|
ad7775740b | ||
|
|
965e927e3a | ||
|
|
c26e06e0b7 | ||
|
|
d05b3513e8 | ||
|
|
4b51164964 | ||
|
|
296674fbee | ||
|
|
546937d338 | ||
|
|
30b9ff2ece | ||
|
|
dbd0175159 | ||
|
|
57b94f0b4d | ||
|
|
366d5f710b | ||
|
|
cf689a4708 | ||
|
|
42b41f84ff | ||
|
|
faccdac486 | ||
|
|
5aa625bf7c | ||
|
|
acd414f017 | ||
|
|
57e8fb5b7b | ||
|
|
4a685f5d5f | ||
|
|
c0c2283cfb | ||
|
|
a3ce3d76fc | ||
|
|
a83ed0a6e3 | ||
|
|
5ad59ea2e1 | ||
|
|
e7574ccd14 | ||
|
|
97bb7ca405 | ||
|
|
db815e4749 | ||
|
|
b39a79f526 | ||
|
|
f5430c1700 | ||
|
|
886f99f886 | ||
|
|
44ffc903d4 | ||
|
|
705ad0638d | ||
|
|
10591d1d6b | ||
|
|
8a71953275 | ||
|
|
e9f6fdff80 | ||
|
|
9f8f4033be | ||
|
|
2957fa9bbe | ||
|
|
3ca906cc4f | ||
|
|
6055e4faac | ||
|
|
8e17f9a1a2 | ||
|
|
c1e1a167c4 | ||
|
|
d88d765e24 | ||
|
|
126fbd958c | ||
|
|
582d3b2ad0 | ||
|
|
2c88e66852 | ||
|
|
e2e95f08ab | ||
|
|
b7295b2443 | ||
|
|
1124981c81 | ||
|
|
c6b399f5b2 | ||
|
|
97adf6a8b0 | ||
|
|
ba8e444d9b | ||
|
|
6f2ec52e8d | ||
|
|
b88b0271df | ||
|
|
5c2b395324 | ||
|
|
475f380fd0 | ||
|
|
8972b62517 | ||
|
|
9b0b08f368 | ||
|
|
20f24b3e60 | ||
|
|
02a654dcb3 | ||
|
|
b0ab3f7fa4 | ||
|
|
5c7f7d393c | ||
|
|
67455254db | ||
|
|
c0d5f15b5b | ||
|
|
9fb91309b6 | ||
|
|
eb8ce2d3fe | ||
|
|
83e91deb87 | ||
|
|
137df23a74 | ||
|
|
2c1e370bbe | ||
|
|
ad85a77611 | ||
|
|
d59bf93cd5 | ||
|
|
a8382c91eb | ||
|
|
ffae5ad1e5 | ||
|
|
cb9c6e7fd2 | ||
|
|
cbfe0b7514 | ||
|
|
62583201b8 | ||
|
|
f5f1bde1fe | ||
|
|
25788dc238 | ||
|
|
00bd94d71b | ||
|
|
705f071162 | ||
|
|
65841b3cb2 | ||
|
|
c9397f095b | ||
|
|
d12e1ac4c7 | ||
|
|
14d1cff22e | ||
|
|
97ddfc7edc | ||
|
|
a82088ad7c | ||
|
|
59f12cb329 | ||
|
|
dd1735fb76 | ||
|
|
b2b7c0986f | ||
|
|
3c2a3c68bc | ||
|
|
b6c2d7bd1a | ||
|
|
12e41941c4 | ||
|
|
c376ae84bb | ||
|
|
8e43b5be25 | ||
|
|
a720b14fa5 | ||
|
|
e68df67aee | ||
|
|
68f9c62c9a | ||
|
|
74ea6fcdc5 | ||
|
|
f7ac879011 | ||
|
|
ac5f28ad4f | ||
|
|
51aaf4c152 | ||
|
|
00d327b867 | ||
|
|
64d948db25 | ||
|
|
aa756f22f3 | ||
|
|
e5ecf1affc | ||
|
|
d567eb3fdd | ||
|
|
c5a48a5187 | ||
|
|
c82152068e | ||
|
|
f3b29cb835 | ||
|
|
55ba174de1 | ||
|
|
2a11159182 | ||
|
|
331032998b | ||
|
|
80bb0741c4 | ||
|
|
8fe104f43c | ||
|
|
6bf0b3fe55 | ||
|
|
535d8ce536 | ||
|
|
c76c8dc599 | ||
|
|
eceaa5d60b | ||
|
|
963dfb18a3 | ||
|
|
99dbe371ae | ||
|
|
d901058cf5 | ||
|
|
79dbc4c9c1 | ||
|
|
d666be4c93 | ||
|
|
8066c03641 | ||
|
|
805e6f58a4 | ||
|
|
555c9f67cc | ||
|
|
c2b55a14d0 | ||
|
|
4da4c4996d | ||
|
|
21a2b63618 | ||
|
|
0d6ec2ea7c | ||
|
|
03ebe92572 | ||
|
|
7dc43d301e | ||
|
|
c2ad57cabe | ||
|
|
572c584994 | ||
|
|
41fe4b00a2 | ||
|
|
bf3dd47e4b | ||
|
|
af05d42935 | ||
|
|
5bed387c5d | ||
|
|
7b28dd3640 | ||
|
|
4564c2fb3c | ||
|
|
c356afc8ae | ||
|
|
f0017e828b | ||
|
|
b9736e8cb3 | ||
|
|
b93b55fc19 | ||
|
|
413809e792 | ||
|
|
52d23fdb54 | ||
|
|
d0d43c797d | ||
|
|
190dea598e | ||
|
|
1f298b5164 | ||
|
|
87748c6714 | ||
|
|
7040f03087 | ||
|
|
5e49c84903 | ||
|
|
411e98f7bd | ||
|
|
fd5f298649 | ||
|
|
6d0ced0bbd | ||
|
|
7548e2ca9b | ||
|
|
a840fdc792 | ||
|
|
1ee4088568 | ||
|
|
f6b3204f32 | ||
|
|
468e6e61c5 | ||
|
|
b5885f39cb | ||
|
|
b2e518cf3d | ||
|
|
5eaa6cbe16 | ||
|
|
30bd8a33cc | ||
|
|
64c02b816d | ||
|
|
de1a4a5c8f | ||
|
|
4cda03b3ab | ||
|
|
47b95de178 | ||
|
|
d022fba681 | ||
|
|
08facca76b | ||
|
|
5a74dd4e9b | ||
|
|
fa33580bfe | ||
|
|
0c3a3fcf6e | ||
|
|
8e0fabdd4d | ||
|
|
084b18284a | ||
|
|
996eb824d4 | ||
|
|
c0b69c1fc1 | ||
|
|
bc5cfab36e | ||
|
|
f2d5f877ed | ||
|
|
4c2784aef7 | ||
|
|
dd7e6f6eff | ||
|
|
200f33bd97 | ||
|
|
8021b391a8 | ||
|
|
6b740f3616 | ||
|
|
5d8544b28f | ||
|
|
0516d71472 | ||
|
|
f166dcb07d | ||
|
|
7027d219d3 | ||
|
|
10555a1573 | ||
|
|
6e8be79bd0 | ||
|
|
532c178c02 | ||
|
|
d688241275 | ||
|
|
cfeda020db | ||
|
|
0bfdb0b520 | ||
|
|
bfa230f697 | ||
|
|
b35a5f360c | ||
|
|
b3ddf16b35 | ||
|
|
3fb27158d1 | ||
|
|
1407a6e17b | ||
|
|
b93b3ce476 | ||
|
|
76cc626d27 | ||
|
|
79f783923e | ||
|
|
892f49bf79 | ||
|
|
4b29529f0a | ||
|
|
c2be671310 | ||
|
|
4e3d2f9610 | ||
|
|
63958e12e7 | ||
|
|
df66fd3064 | ||
|
|
1bf1edda0f | ||
|
|
0990e424eb | ||
|
|
17d613366b | ||
|
|
ae1cf8170a | ||
|
|
aaf0201077 | ||
|
|
e4dc98bd0f | ||
|
|
7ed5640edf | ||
|
|
51eae9d80d | ||
|
|
5cf4a138db | ||
|
|
1130ffd81d | ||
|
|
7b6cf7f227 | ||
|
|
f672bd7c3e | ||
|
|
afb05da665 | ||
|
|
8cf3bb5a1c | ||
|
|
2129c77377 | ||
|
|
ad29078090 | ||
|
|
7fc2b0d2c0 | ||
|
|
8ec893b0ff | ||
|
|
107624e68f | ||
|
|
30120f3bf3 | ||
|
|
cae4f588cd | ||
|
|
dc97649efb | ||
|
|
f2506eef06 | ||
|
|
2fed290399 | ||
|
|
6bfe90ed3e | ||
|
|
6743f2582a | ||
|
|
b8643e0ddc | ||
|
|
9b41cf30df | ||
|
|
f9e0e1dd63 | ||
|
|
a062a3acd6 | ||
|
|
9bc88c9dd5 | ||
|
|
6b9f5ba08e | ||
|
|
8f9db769d4 | ||
|
|
a7ca676565 | ||
|
|
aea3ae742a | ||
|
|
f7567b489e | ||
|
|
28b0ecda64 | ||
|
|
283dd81b73 | ||
|
|
05568ad4bf | ||
|
|
9ea8f0c54b | ||
|
|
be645a8f75 | ||
|
|
898a5051d5 | ||
|
|
af5b9e3461 | ||
|
|
d43131a35c | ||
|
|
b1eb8c9c4e | ||
|
|
9e77e1010e | ||
|
|
9af5aa2e1a | ||
|
|
4813ad2a34 | ||
|
|
e44fe5007e | ||
|
|
51f1c0ada3 | ||
|
|
78cd7aa78b | ||
|
|
4d2ab6c7ba | ||
|
|
22b413dd54 | ||
|
|
2e290a1a1d | ||
|
|
33bdbc3810 | ||
|
|
d645d47ba8 | ||
|
|
b2b1926d4a | ||
|
|
2628c2130e | ||
|
|
8121b7aa85 | ||
|
|
6754b7e058 | ||
|
|
29da94f0e0 | ||
|
|
481381c9fb | ||
|
|
71c946f404 | ||
|
|
6ab48b40ff | ||
|
|
ea29c749fa | ||
|
|
39e5214785 |
21
#licenses/LinkCableMultiboot/gba-mem-viewer.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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.
|
||||
7
#licenses/LinkCard/4-e.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
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.
|
||||
7
#licenses/LinkPS2Mouse/PS2-Mouse-Arduino.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Arduino/Wiring Library for interfacing with a PS2 mouse.
|
||||
|
||||
Homepage
|
||||
github.com/kristopher/PS2-Mouse-Arduino
|
||||
|
||||
License
|
||||
MIT
|
||||
44
#licenses/examples/LinkUniversal_real/assets/graphics.txt
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
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
|
||||
11
#licenses/examples/LinkUniversal_real/assets/music.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
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
|
||||
17
#licenses/examples/LinkUniversal_real/code/butano/agbabi.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
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.
|
||||
19
#licenses/examples/LinkUniversal_real/code/butano/butano.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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.
|
||||
21
#licenses/examples/LinkUniversal_real/code/butano/ctti.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
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/.
|
||||
21
#licenses/examples/LinkUniversal_real/code/butano/etl.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
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.
|
||||
23
#licenses/examples/LinkUniversal_real/code/butano/maxmod.txt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/****************************************************************************
|
||||
* __ *
|
||||
* ____ ___ ____ __ ______ ___ ____ ____/ / *
|
||||
* / __ `__ \/ __ `/ |/ / __ `__ \/ __ \/ __ / *
|
||||
* / / / / / / /_/ /> </ / / / / / /_/ / /_/ / *
|
||||
* /_/ /_/ /_/\__,_/_/|_/_/ /_/ /_/\____/\__,_/ *
|
||||
* *
|
||||
* 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. *
|
||||
****************************************************************************/
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
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.
|
||||
19
#licenses/examples/LinkUniversal_real/code/butano/ugba.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
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.
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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
|
|
@ -5,6 +5,7 @@ node_modules/
|
|||
|
||||
# Files
|
||||
examples/multiboot
|
||||
loader.gbfs
|
||||
.DS_Store
|
||||
*.elf
|
||||
*.gba
|
||||
|
|
|
|||
3
.prettierignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# ignore all files except *.md
|
||||
*
|
||||
!*.md
|
||||
4
.prettierrc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Rodrigo Alfonso
|
||||
Copyright (c) 2020-2025 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
|
||||
|
|
|
|||
260
docs/e_reader.md
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
- 🌎 **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.
|
||||
59
docs/img/dependencies.graphviz.txt
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
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
|
||||
}
|
||||
204
docs/img/dependencies.svg
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<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->LinkRawCable -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>LinkCable->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->LinkRawCable -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>LinkCableMultiboot->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->LinkSPI -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>LinkCableMultiboot->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->LinkRawWireless -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>LinkWireless->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->LinkRawWireless -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>LinkWirelessMultiboot->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->LinkWirelessOpenSDK -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>LinkWirelessMultiboot->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->LinkGPIO -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>LinkRawWireless->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->LinkSPI -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>LinkRawWireless->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->LinkRawWireless -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>LinkWirelessOpenSDK->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->LinkCable -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>LinkUniversal->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->LinkWireless -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>LinkUniversal->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->LinkRawCable -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>LinkCard->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->LinkSPI -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>LinkCard->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->LinkGPIO -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>LinkMobile->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->LinkSPI -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>LinkMobile->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->LinkGPIO -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>LinkIR->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>
|
||||
|
After Width: | Height: | Size: 9.8 KiB |
BIN
docs/img/link-card.gif
Normal file
|
After Width: | Height: | Size: 666 KiB |
|
Before Width: | Height: | Size: 812 KiB After Width: | Height: | Size: 669 KiB |
BIN
docs/img/link-ir.gif
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 906 KiB After Width: | Height: | Size: 1023 KiB |
BIN
docs/img/wireless/ack-inverted.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
172
docs/infrared_adapter.md
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
🌎 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.
|
||||
|
|
@ -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,14 +144,15 @@ 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.
|
||||
|
||||
|
|
@ -343,7 +344,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**
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ _You can make this screen display any game_
|
|||
|
||||
When I started, I used the following resources to start being able to talk with the wireless adapter:
|
||||
|
||||
- [This Gist contains some details](wireless.txt)
|
||||
- [This Gist contains some details](https://gist.github.com/iracigt/50b3a857e4d82c2c11d0dd5f84ecac6b)
|
||||
- [GBATEK has a section on the wireless adapter](gbatek.md)
|
||||
|
||||
## Pinout
|
||||
|
|
@ -237,13 +237,13 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
[](img/wireless/0x16.png)
|
||||
|
||||
- Send length: 6, response length: 0
|
||||
- The data to be broadcast out to all adapters. Examples of use include the union room, broadcasting game name and username in download play, and the username in direct multiplayer in Pokémon.
|
||||
- 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.
|
||||
|
||||
💻 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:
|
||||
💻 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:
|
||||
|
||||
[](img/wireless/broadcast.png)
|
||||
|
||||
(if you read from right to left, it says `ICE CLIMBER` - `NINTENDO`)
|
||||
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).
|
||||
|
||||
🆔 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,20 +254,27 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
- 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 session and stop allowing new clients, but also **keeping the existing connections alive**. Sends and Receives still work, but:
|
||||
- This command stops host broadcast. This allows to "close" the room and stop allowing new clients, but also **keeping the existing connections alive**. Sends and Receives still work, but:
|
||||
- Clients cannot connect, even if they already know the host ID (`FinishConnection` will fail).
|
||||
- Calls to `AcceptConnections` on the host side will fail, unless `StartHost` is called again.
|
||||
- Calls to `PollConnections` on the host side will fail, unless `StartHost` is called again.
|
||||
|
||||
#### BroadcastRead - `0x1c`, `0x1d` and `0x1e`
|
||||
|
||||
[](img/wireless/0x1d.png)
|
||||
|
||||
- Send length: 0, response length: 7 \* number of broadcasts (maximum: 4)
|
||||
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)
|
||||
- All currently broadcasting devices are returned here along with a word of **metadata** (the metadata word first, then 6 words with broadcast data).
|
||||
- The metadata contains:
|
||||
- The metadata word 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.
|
||||
|
|
@ -281,20 +288,24 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
|
||||
🆔 IDs are randomly generated. Each time you broadcast or connect, the adapter assigns you a new ID.
|
||||
|
||||
✅ Reading broadcasts is a three-step process: First, you send `0x1c` (you will get an ACK instantly), and start waiting until the adapter retrieves data (games usually wait 1 full second). Then, send a `0x1d` and it will return what's described above. Lastly, send a `0x1e` to finish the process (you can ignore what the adapter returns here). If you don't send that last `0x1e`, the next command will fail.
|
||||
✅ Reading broadcasts is a three-step process: First, you send `0x1c` (you will get an ACK instantly and no data) to put the adapter in 'broadcast reading' mode, and start waiting until the adapter retrieves data (games usually wait 1 full second). Then, send a `0x1d` and it will return what's described above. Lastly, send a `0x1e` to finish the process (you can ignore what the adapter returns here), which exits broadcast reading mode. If you don't send that last `0x1e`, the next command will fail.
|
||||
|
||||
⌚ Although games wait 1 full second, small waits (like ~160ms) also work.
|
||||
|
||||
⚙️ Calling `0x1d` repeatedly will provide an updated list of up to 4 hosts, always in the same order within each call. 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.
|
||||
|
||||
#### AcceptConnections - `0x1a`
|
||||
#### PollConnections - `0x1a`
|
||||
|
||||
- Send length: 0, response length: 0+
|
||||
- Accepts new connections and returns a list with the connected adapters. The length of the response is zero if there are no connected adapters.
|
||||
- It includes one value per connected client, in which the most significant byte is the `clientNumber` (see [IsFinishedConnect](#isfinishedconnect---0x20)) and the least significant byte is the ID.
|
||||
- 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.
|
||||
|
||||
🔗 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`
|
||||
|
||||
[](img/wireless/0x1f.png)
|
||||
|
|
@ -302,7 +313,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
- Send length: 1, response length: 0
|
||||
- Send the ID of the adapter you want to connect to from [BroadcastRead](#broadcastread---0x1c-0x1d-and-0x1e).
|
||||
|
||||
#### IsFinishedConnect - `0x20`
|
||||
#### IsConnectionComplete - `0x20`
|
||||
|
||||
[](img/wireless/0x20.png)
|
||||
|
||||
|
|
@ -318,7 +329,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
[](img/wireless/0x21.png)
|
||||
|
||||
- Send length: 0, response length: 1
|
||||
- Called after [IsFinishedConnect](#isfinishedconnect---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
|
||||
- Called after [IsConnectionComplete](#isconnectioncomplete---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
|
||||
|
||||
#### SendData - `0x24`
|
||||
|
||||
|
|
@ -329,7 +340,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
|
||||
- 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 [IsFinishedConnect](#isfinishedconnect---0x20). For example, if we want to send a single 4-byte value (`0xaabbccdd`):
|
||||
- For clients: `(bytes << (3 + (1+clientNumber) * 5))`. The `clientNumber` is what I described in [IsConnectionComplete](#isconnectioncomplete---0x20). For example, if we want to send a single 4-byte value (`0xaabbccdd`):
|
||||
- The first client should send: `0x400`, `0xaabbccdd`
|
||||
- The second client should send: `0x8000`, `0xaabbccdd`
|
||||
- The third client should send: `0x100000`, `0xaabbccdd`
|
||||
|
|
@ -367,7 +378,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
- **Host**: `ReceiveData`
|
||||
- Receives `{rcvHeader}`, 20
|
||||
|
||||
🔁 This command can also be used with one header and **no data**. In this case, it will resend the last N bytes (based on the header) of the last packet. 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), 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**.
|
||||
|
||||
#### SendDataWait - `0x25`
|
||||
|
||||
|
|
@ -412,7 +423,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
[](img/wireless/0x30.png)
|
||||
|
||||
- Send length 1, reponse length: 0
|
||||
- This command disconnects clients. The argument is a bitmask of the client ID to disconnect. Sending `0x1` means "disconnect client number 0", sending `0x2` means "disconnect client number 1", and sending `0xF` would disconnect all the clients. After disconnecting a client, its ID won't appear on `AcceptConnection` calls and its `clientNumber` will be liberated, so other peers can connect.
|
||||
- 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.
|
||||
|
||||
⚡ 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:
|
||||
|
||||
|
|
@ -450,7 +461,8 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
* Bits `16-23`: A 4-bit array with slots. If the console is a client, it'll have a 1 in the position assigned to that slot (e.g. the one with `clientNumber` 3 will have `0100`). The host will always have `0000` here.
|
||||
* Bits `24-31`: A number indicating the state of the adapter
|
||||
- `0` = idle
|
||||
- `1`/`2` = serving (host)
|
||||
- `1` = serving (host), closed room
|
||||
- `2` = serving (host), open room
|
||||
- `3` = searching
|
||||
- `4` = connecting
|
||||
- `5` = connected (client)
|
||||
|
|
@ -459,10 +471,10 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
|
|||
|
||||
- Send length: 0, Response length: 1+
|
||||
|
||||
- It's returns a list of the connected adapters, similar to what `AcceptConnections` responds, but also:
|
||||
- It's returns a list of the connected adapters, similar to what `PollConnections` responds, but also:
|
||||
|
||||
- `SlotStatus` has an extra word at the start of the response, indicating the `clientNumber` that the next connection will have (or `0xFF` if the room is not accepting new clients).
|
||||
- `SlotStatus` can be called after `EndHost`, while `AcceptConnections` fails.
|
||||
- `SlotStatus` can be called after `EndHost`, while `PollConnections` fails.
|
||||
|
||||
#### ConfigStatus - `0x15`
|
||||
|
||||
|
|
@ -515,7 +527,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.
|
||||
- Bits `8-11`: The clients marked as _inactive_. This depends on the # of maximum transmissions configured with the [Setup](#setup---0x17) command. It only marks them as inactive after 4 seconds.
|
||||
|
||||
🔗 When the adapter is disconnected from the host, it sends a `0x99660029`.
|
||||
|
||||
|
|
@ -527,12 +539,16 @@ 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
|
||||
|
||||
1. The adapter goes low as soon as it can.
|
||||
2. The GBA goes high.
|
||||
3. The adapter goes high.
|
||||
4. The GBA goes low _when it’s ready_.
|
||||
5. The adapter goes low when it's ready.
|
||||
6. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
|
||||
[](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 it’s ready_, but **wait at least 40us**! (\*)
|
||||
5. The adapter goes low when it's ready.
|
||||
6. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
|
||||
|
||||
> (\*) 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.
|
||||
|
||||
## Wireless Multiboot
|
||||
|
||||
|
|
@ -540,13 +556,15 @@ 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.
|
||||
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.
|
||||
|
||||
### Valid header
|
||||
|
||||
|
|
@ -613,6 +631,7 @@ 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
|
||||
|
||||
|
|
@ -632,19 +651,23 @@ 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: `MB-DL`
|
||||
- Payload: `0x4D`, `0x42`, `0x2D`, `0x44`, `0x4C`, `0x00`
|
||||
- => `MB-DL`
|
||||
- Server: ACKs the packet
|
||||
- Client: sends `0x000008C6`, `0x50000000`
|
||||
- Header: `0x08C6` (`size=6, n=1, ph=2, ack=0, commState=2`)
|
||||
- Payload: `P`
|
||||
- Payload: `0x00`, `0x00`, `0x00`, `0x00`, `0x00`, `0x00`, `0x50`
|
||||
- => `P`
|
||||
- Server: ACKs the packet
|
||||
- Client: sends `0x414C08E6`, `0x20524559`
|
||||
- Header: `0x08E6` (`size=6, n=1, ph=3, ack=0, commState=2`)
|
||||
- Payload: `LAYER`
|
||||
- Payload: `0x4C`, `0x41`, `0x59`, `0x45`, `0x52`, `0x20`
|
||||
- => `LAYER `
|
||||
- Server: ACKs the packet
|
||||
- Client: sends `0x00410902`
|
||||
- Header: `0x0902` (`size=2, n=2, ph=0, ack=0, commState=2`)
|
||||
- Payload: `A`
|
||||
- Payload: `0x41`, `0x00`
|
||||
- => `A`
|
||||
- Server: ACKs the packet
|
||||
- Client: sends `0x00000C00`
|
||||
- Header: `0x0C00` (`size=0, n=0, ph=0, ack=0, commState=3`) (`3 = ENDING`)
|
||||
|
|
@ -653,13 +676,15 @@ enum CommState : unsigned int {
|
|||
- Client: sends `0x00000080`
|
||||
- Header: `0x0080` (`size=0, n=1, ph=0, ack=0, commState=0`) (`0 = OFF`)
|
||||
- No payload
|
||||
- Server: ACKs the packet
|
||||
- Server: Ghost send _(OFF state doesn't expect an ack!)_
|
||||
|
||||
## (2) ROM start command
|
||||
|
||||
- Server: sends `0x00044807`, `0x00000054`, `0x00000002`
|
||||
- Header: `0x044807` (`size=7, n=1, ph=0, ack=0, commState=1`) (`1 = STARTING`)
|
||||
- Payload: `0x00`, `0x54`, `0x00`, `0x00`, `0x00`, `0x02`, `0x00`
|
||||
- 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`
|
||||
- Client: ACKs the packet (`size=0, n=1, ph=0, ack=1, commState=1`)
|
||||
|
||||
## (3) ROM bytes
|
||||
|
|
@ -675,6 +700,7 @@ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib ../_lib/libgbfs
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code ../_lib/libgbfs
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkCableMultiboot.hpp"
|
||||
|
||||
#include <string.h>
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
extern "C" {
|
||||
#include "../../_lib/libgbfs/gbfs.h"
|
||||
|
|
@ -13,10 +13,6 @@ 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();
|
||||
|
||||
|
|
@ -33,11 +29,10 @@ void selectRight() {
|
|||
}
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
|
||||
irq_init(NULL);
|
||||
irq_add(II_VBLANK, NULL);
|
||||
interrupt_init();
|
||||
interrupt_add(INTR_VBLANK, []() {});
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -45,16 +40,16 @@ int main() {
|
|||
|
||||
// Ensure there are GBFS files
|
||||
if (fs == NULL) {
|
||||
log("! GBFS file not found");
|
||||
Common::log("! GBFS file not found");
|
||||
while (true)
|
||||
;
|
||||
} else if (gbfs_get_nth_obj(fs, 0, NULL, NULL) == NULL) {
|
||||
log("! No files found (GBFS)");
|
||||
Common::log("! No files found (GBFS)");
|
||||
while (true)
|
||||
;
|
||||
}
|
||||
|
||||
bool left = false, right = false, a = false, b = false, l = false;
|
||||
bool left = true, right = true, a = true, b = true, l = true;
|
||||
|
||||
while (true) {
|
||||
// Get selected ROM name
|
||||
|
|
@ -70,17 +65,18 @@ int main() {
|
|||
}
|
||||
|
||||
// Toggle mode
|
||||
if (didPress(KEY_L, l))
|
||||
if (Common::didPress(KEY_L, l))
|
||||
spi = !spi;
|
||||
|
||||
// Select ROM
|
||||
if (didPress(KEY_LEFT, left))
|
||||
if (Common::didPress(KEY_LEFT, left))
|
||||
selectLeft();
|
||||
if (didPress(KEY_RIGHT, right))
|
||||
if (Common::didPress(KEY_RIGHT, right))
|
||||
selectRight();
|
||||
|
||||
// Menu
|
||||
log("LinkCableMultiboot_demo\n (v7.0.1)\n\n"
|
||||
Common::log(
|
||||
"LinkCableMultiboot_demo\n (v8.0.3)\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 " +
|
||||
|
|
@ -88,8 +84,8 @@ int main() {
|
|||
std::string(spi ? "SPI (GBC cable)" : "MULTI_PLAY (GBA cable)"));
|
||||
|
||||
// Send ROM
|
||||
if (didPress(KEY_A, a)) {
|
||||
log("Sending... (SELECT to cancel)");
|
||||
if (Common::didPress(KEY_A, a)) {
|
||||
Common::log("Sending... (SELECT to cancel)");
|
||||
|
||||
// (2) Send the ROM
|
||||
auto result = linkCableMultiboot->sendRom(
|
||||
|
|
@ -102,14 +98,14 @@ int main() {
|
|||
: LinkCableMultiboot::TransferMode::MULTI_PLAY);
|
||||
|
||||
// Print results and wait
|
||||
log("Result: " + std::to_string(result) + "\n" +
|
||||
"Press DOWN to continue...");
|
||||
waitFor(KEY_DOWN);
|
||||
Common::log("Result: " + std::to_string((int)result) + "\n" +
|
||||
"Press DOWN to continue...");
|
||||
Common::waitForKey(KEY_DOWN);
|
||||
}
|
||||
|
||||
// Launch ROM
|
||||
if (didPress(KEY_B, b)) {
|
||||
log("Launching...");
|
||||
if (Common::didPress(KEY_B, b)) {
|
||||
Common::log("Launching...");
|
||||
VBlankIntrWait();
|
||||
|
||||
REG_IME = 0;
|
||||
|
|
@ -119,7 +115,7 @@ int main() {
|
|||
(const u8*)gbfs_get_nth_obj(fs, selectedFile, NULL, &fileLength);
|
||||
|
||||
void* EWRAM = (void*)0x02000000;
|
||||
memcpy(EWRAM, romToSend, fileLength);
|
||||
std::memcpy(EWRAM, romToSend, fileLength);
|
||||
|
||||
asm volatile(
|
||||
"mov r0, %0\n"
|
||||
|
|
@ -137,28 +133,3 @@ 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -4,27 +4,20 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkCable.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
void log(std::string text);
|
||||
|
||||
// (1) Create a LinkCable instance
|
||||
LinkCable* linkCable = new LinkCable();
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
|
||||
// (2) Add the required interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_CABLE_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_CABLE_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
interrupt_add(INTR_VBLANK, LINK_CABLE_ISR_VBLANK);
|
||||
interrupt_add(INTR_SERIAL, LINK_CABLE_ISR_SERIAL);
|
||||
interrupt_add(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
|
||||
|
||||
// (3) Initialize the library
|
||||
linkCable->activate();
|
||||
|
|
@ -45,7 +38,7 @@ int main() {
|
|||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
linkCable->send(keys + 1); // (avoid using 0)
|
||||
|
||||
std::string output = "LinkCable_basic (v7.0.1)\n\n";
|
||||
std::string output = "LinkCable_basic (v8.0.3)\n\n";
|
||||
if (linkCable->isConnected()) {
|
||||
u8 playerCount = linkCable->playerCount();
|
||||
u8 currentPlayerId = linkCable->currentPlayerId();
|
||||
|
|
@ -67,14 +60,8 @@ int main() {
|
|||
}
|
||||
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::log(output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log(std::string text) {
|
||||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,9 +123,8 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba -lgba-sprite-engine
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib \
|
||||
src/scenes \
|
||||
src/utils
|
||||
SRCDIRS := src ../_lib ../_lib/libgba-sprite-engine ../../lib ../../lib/iwram_code \
|
||||
src/scenes
|
||||
DATADIRS :=
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
|
||||
|
|
@ -166,8 +165,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -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,18 +40,74 @@ 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;
|
||||
|
||||
// log player ID/count and important flags
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
TextStream::instance().setText(
|
||||
"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);
|
||||
"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
|
||||
|
||||
engine->update();
|
||||
|
||||
|
|
@ -61,43 +117,33 @@ 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_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);
|
||||
interrupt_add(INTR_VBLANK, LINK_CABLE_ISR_VBLANK);
|
||||
interrupt_add(INTR_SERIAL, LINK_CABLE_ISR_SERIAL);
|
||||
interrupt_add(INTR_TIMER3, LINK_CABLE_ISR_TIMER);
|
||||
#else
|
||||
// LinkUniversal
|
||||
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);
|
||||
interrupt_add(INTR_VBLANK, LINK_UNIVERSAL_ISR_VBLANK);
|
||||
interrupt_add(INTR_SERIAL, LINK_UNIVERSAL_ISR_SERIAL);
|
||||
interrupt_add(INTR_TIMER3, LINK_UNIVERSAL_ISR_TIMER);
|
||||
#endif
|
||||
|
||||
// A+B+START+SELECT
|
||||
// A+B+START+SELECT = SoftReset
|
||||
#if MULTIBOOT_BUILD == 0
|
||||
REG_KEYCNT = 0b1100000000001111;
|
||||
interrupt_set_handler(INTR_KEYPAD, ISR_reset);
|
||||
interrupt_enable(INTR_KEYPAD);
|
||||
interrupt_add(INTR_KEYPAD, Common::ISR_reset);
|
||||
#endif
|
||||
}
|
||||
|
||||
void printTutorial() {
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
DEBULOG("LinkCable_full (v7.0.1)");
|
||||
DEBULOG("LinkCable_full (v8.0.3)");
|
||||
#else
|
||||
DEBULOG("LinkUniversal_full (v7.0.1)");
|
||||
DEBULOG("LinkUniversal_full (v8.0.3)");
|
||||
#endif
|
||||
|
||||
DEBULOG("");
|
||||
|
|
@ -108,7 +154,10 @@ void printTutorial() {
|
|||
DEBULOG("A: send counter++ (cont)");
|
||||
DEBULOG("L: send counter++ twice (once)");
|
||||
DEBULOG("R: send counter++ twice (cont)");
|
||||
DEBULOG("SELECT: force lag (9k lines)");
|
||||
#ifdef USE_LINK_UNIVERSAL
|
||||
DEBULOG("RIGHT: get signal level");
|
||||
#endif
|
||||
DEBULOG("SELECT: force lag (5 frames)");
|
||||
DEBULOG("DOWN: turn off connection");
|
||||
DEBULOG("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,13 +3,17 @@
|
|||
|
||||
// #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 <tonc.h>
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/libgba-sprite-engine/scene.h"
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
extern LinkCable* linkConnection;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
#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) {}
|
||||
|
||||
|
|
@ -18,12 +16,16 @@ 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("-> " + asStr(data));
|
||||
DEBULOG("-> " + std::to_string(data));
|
||||
linkConnection->send(data);
|
||||
}
|
||||
|
||||
void printWirelessSignalLevel();
|
||||
|
||||
std::vector<Background*> TestScene::backgrounds() {
|
||||
return {};
|
||||
}
|
||||
|
|
@ -53,22 +55,27 @@ 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 (" + asStr(linkConnection->playerCount()) +
|
||||
DEBULOG("! connected (" + std::to_string(linkConnection->playerCount()) +
|
||||
" players)");
|
||||
}
|
||||
if (isConnected && !linkConnection->isConnected()) {
|
||||
isConnected = false;
|
||||
DEBULOG("! disconnected");
|
||||
}
|
||||
if (selectHandler->hasBeenPressedNow()) {
|
||||
|
||||
// other buttons
|
||||
if (selectHandler->getIsPressed()) {
|
||||
DEBULOG("! lagging...");
|
||||
SCENE_wait(9000);
|
||||
Link::wait(228 * 5);
|
||||
}
|
||||
if (rightHandler->hasBeenReleasedNow())
|
||||
printWirelessSignalLevel();
|
||||
|
||||
// determine which value should be sent
|
||||
u16 value = LINK_CABLE_NO_DATA;
|
||||
|
|
@ -98,9 +105,38 @@ void TestScene::tick(u16 keys) {
|
|||
while (linkConnection->canRead(i)) {
|
||||
u16 message = linkConnection->read(i);
|
||||
if (i != linkConnection->currentPlayerId())
|
||||
DEBULOG("<-p" + asStr(i) + ": " + asStr(message) + " (frame " +
|
||||
asStr(frameCounter) + ")");
|
||||
DEBULOG("<-p" + std::to_string(i) + ": " + std::to_string(message) +
|
||||
" (frame " + std::to_string(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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
#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
|
||||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src lib
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
// - 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.
|
||||
|
|
@ -13,9 +12,11 @@
|
|||
// - 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
|
||||
|
|
@ -23,13 +24,15 @@
|
|||
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();
|
||||
void profileStart();
|
||||
u32 profileStop();
|
||||
u32 toMs(u32 cycles);
|
||||
|
||||
u32 vblankTime = 0;
|
||||
u32 serialTime = 0;
|
||||
u32 timerTime = 0;
|
||||
u32 vblankIRQs = 0;
|
||||
u32 serialIRQs = 0;
|
||||
u32 timerIRQs = 0;
|
||||
u32 avgTime = 0;
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
LinkCable* linkCable = new LinkCable();
|
||||
|
|
@ -39,7 +42,7 @@ LinkUniversal* linkUniversal =
|
|||
new LinkUniversal(LinkUniversal::Protocol::AUTODETECT,
|
||||
"LinkUniversal",
|
||||
(LinkUniversal::CableOptions){
|
||||
.baudRate = LinkCable::BAUD_RATE_1,
|
||||
.baudRate = LinkCable::BaudRate::BAUD_RATE_1,
|
||||
.timeout = LINK_CABLE_DEFAULT_TIMEOUT,
|
||||
.interval = LINK_CABLE_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_CABLE_DEFAULT_SEND_TIMER_ID},
|
||||
|
|
@ -48,83 +51,131 @@ LinkUniversal* linkUniversal =
|
|||
.maxPlayers = 2,
|
||||
.timeout = LINK_WIRELESS_DEFAULT_TIMEOUT,
|
||||
.interval = LINK_WIRELESS_DEFAULT_INTERVAL,
|
||||
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID},
|
||||
__qran_seed);
|
||||
.sendTimerId = LINK_WIRELESS_DEFAULT_SEND_TIMER_ID});
|
||||
LinkUniversal* linkConnection = linkUniversal;
|
||||
#endif
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
u16 getInterval() {
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
return linkConnection->config.interval;
|
||||
#else
|
||||
return linkConnection->getLinkCable()->config.interval;
|
||||
#endif
|
||||
}
|
||||
|
||||
interrupt_init();
|
||||
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;
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
// LinkCable
|
||||
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);
|
||||
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);
|
||||
#else
|
||||
// LinkUniversal
|
||||
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);
|
||||
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);
|
||||
#endif
|
||||
}
|
||||
|
||||
void init() {
|
||||
Common::initTTE();
|
||||
|
||||
interrupt_init();
|
||||
setUpInterrupts(false);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
while (true) {
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
std::string output = "LinkCable_stress (v7.0.1)\n\n";
|
||||
std::string output = "LinkCable_stress (v8.0.3)\n\n";
|
||||
#else
|
||||
std::string output = "LinkUniversal_stress (v7.0.1)\n\n";
|
||||
std::string output = "LinkUniversal_stress (v8.0.3)\n\n";
|
||||
Link::randomSeed = __qran_seed;
|
||||
#endif
|
||||
|
||||
linkConnection->deactivate();
|
||||
|
||||
output +=
|
||||
"A: Test packet loss\nB: Test packet sync\nL: Measure ping latency\nR: "
|
||||
"Measure ping-pong latency\n\nLEFT: t=100\nRIGHT: t=25\nDOWN: "
|
||||
"t=200\nUP: t=10\nSTART: Add lag\nSELECT: Reset ";
|
||||
log(output);
|
||||
"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);
|
||||
|
||||
waitFor(KEY_A | KEY_B | KEY_L | KEY_R);
|
||||
Common::waitForKey(KEY_A | KEY_B | KEY_L | KEY_R);
|
||||
u16 initialKeys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
u32 interval = 50;
|
||||
if (initialKeys & KEY_LEFT)
|
||||
interval = 100;
|
||||
if (initialKeys & KEY_RIGHT)
|
||||
interval = 25;
|
||||
if (initialKeys & KEY_DOWN)
|
||||
interval = 200;
|
||||
interval = 100;
|
||||
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
|
||||
interval = 25;
|
||||
setInterval(interval);
|
||||
|
||||
linkConnection->activate();
|
||||
|
||||
if (initialKeys & KEY_A)
|
||||
if (initialKeys & KEY_A) {
|
||||
setUpInterrupts(true);
|
||||
test(false);
|
||||
else if (initialKeys & KEY_B)
|
||||
} else if (initialKeys & KEY_B) {
|
||||
setUpInterrupts(true);
|
||||
test(true);
|
||||
else if (initialKeys & KEY_L)
|
||||
} else if (initialKeys & KEY_L) {
|
||||
setUpInterrupts(false);
|
||||
measureLatency(false);
|
||||
else if (initialKeys & KEY_R)
|
||||
} else if (initialKeys & KEY_R) {
|
||||
setUpInterrupts(false);
|
||||
measureLatency(true);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -135,25 +186,47 @@ void test(bool withSync) {
|
|||
u16 expectedCounter = 0;
|
||||
bool error = false;
|
||||
u16 receivedRemoteCounter = 0;
|
||||
bool increasingInterval = true;
|
||||
bool decreasingInterval = true;
|
||||
|
||||
log("Waiting for data...");
|
||||
Common::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;
|
||||
|
||||
|
|
@ -197,7 +270,9 @@ void test(bool withSync) {
|
|||
}
|
||||
}
|
||||
output += "(" + std::to_string(localCounter) + ", " +
|
||||
std::to_string(expectedCounter) + ")\n";
|
||||
std::to_string(expectedCounter) +
|
||||
")\n\ninterval = " + std::to_string(getInterval()) +
|
||||
"\ncyc/frm = " + std::to_string(avgTime);
|
||||
} else {
|
||||
output += "Waiting...";
|
||||
localCounter = 0;
|
||||
|
|
@ -207,14 +282,14 @@ void test(bool withSync) {
|
|||
}
|
||||
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::log(output);
|
||||
|
||||
if (error) {
|
||||
while (true)
|
||||
if (needsReset())
|
||||
return;
|
||||
} else if (localCounter == FINAL_VALUE && expectedCounter == FINAL_VALUE) {
|
||||
log("Test passed!");
|
||||
Common::log("Test passed!");
|
||||
while (true)
|
||||
if (needsReset())
|
||||
return;
|
||||
|
|
@ -223,17 +298,36 @@ void test(bool withSync) {
|
|||
}
|
||||
|
||||
void measureLatency(bool withPong) {
|
||||
log("Waiting for data...");
|
||||
Common::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();
|
||||
|
||||
|
|
@ -250,36 +344,36 @@ void measureLatency(bool withPong) {
|
|||
|
||||
u32 sentPacket = ++counter;
|
||||
|
||||
profileStart();
|
||||
Common::profileStart();
|
||||
linkConnection->send(sentPacket);
|
||||
if (!linkConnection->waitFor(remotePlayerId, needsReset)) {
|
||||
log("No response! (1) Press DOWN");
|
||||
profileStop();
|
||||
waitFor(KEY_DOWN);
|
||||
Common::log("No response! (1) Press DOWN");
|
||||
Common::profileStop();
|
||||
Common::waitForKey(KEY_DOWN);
|
||||
return;
|
||||
}
|
||||
u16 receivedPacket = linkConnection->read(remotePlayerId);
|
||||
if (withPong) {
|
||||
linkConnection->send(receivedPacket);
|
||||
if (!linkConnection->waitFor(remotePlayerId, needsReset)) {
|
||||
log("No response! (2) Press DOWN");
|
||||
profileStop();
|
||||
waitFor(KEY_DOWN);
|
||||
Common::log("No response! (2) Press DOWN");
|
||||
Common::profileStop();
|
||||
Common::waitForKey(KEY_DOWN);
|
||||
return;
|
||||
}
|
||||
u16 validation = linkConnection->read(remotePlayerId);
|
||||
if (validation != sentPacket) {
|
||||
log("Invalid response! Press DOWN\n value = " +
|
||||
std::to_string(validation) +
|
||||
"\n expected = " + std::to_string(sentPacket));
|
||||
profileStop();
|
||||
waitFor(KEY_DOWN);
|
||||
Common::log("Invalid response! Press DOWN\n value = " +
|
||||
std::to_string(validation) +
|
||||
"\n expected = " + std::to_string(sentPacket));
|
||||
Common::profileStop();
|
||||
Common::waitForKey(KEY_DOWN);
|
||||
return;
|
||||
}
|
||||
}
|
||||
u32 elapsedCycles = profileStop();
|
||||
u32 elapsedCycles = Common::profileStop();
|
||||
|
||||
u32 elapsedMilliseconds = toMs(elapsedCycles);
|
||||
u32 elapsedMilliseconds = Common::toMs(elapsedCycles);
|
||||
samples++;
|
||||
totalMs += elapsedMilliseconds;
|
||||
u32 average = Div(totalMs, samples);
|
||||
|
|
@ -288,12 +382,13 @@ 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);
|
||||
"\nValue sent:\n " + std::to_string(sentPacket) +
|
||||
"\n\ninterval = " + std::to_string(getInterval());
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::log(output);
|
||||
} else {
|
||||
VBlankIntrWait();
|
||||
log("Waiting...");
|
||||
Common::log("Waiting...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -308,56 +403,7 @@ 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include "../../../lib/LinkUniversal.hpp"
|
||||
#endif
|
||||
|
||||
#include <tonc.h>
|
||||
#include "../../_lib/common.h"
|
||||
|
||||
#ifndef USE_LINK_UNIVERSAL
|
||||
extern LinkCable* linkConnection;
|
||||
|
|
|
|||
24
examples/LinkCard_demo/#loader/.editorconfig
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# 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
|
||||
12
examples/LinkCard_demo/#loader/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
.vscode
|
||||
*.bin
|
||||
*.bmp
|
||||
*.elf
|
||||
*.gba
|
||||
*.lnk
|
||||
*.o
|
||||
*.raw
|
||||
*.sa1
|
||||
*.sav
|
||||
*.vpk
|
||||
*.loader
|
||||
87
examples/LinkCard_demo/#loader/Makefile
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# //--------------------------------------------------------//
|
||||
# // 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
|
||||
39
examples/LinkCard_demo/#loader/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# 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
|
||||
```
|
||||
60
examples/LinkCard_demo/#loader/crt0.s
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**********************************/
|
||||
/* 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
|
||||
43
examples/LinkCard_demo/#loader/ereader.ld
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/***********************************/
|
||||
/* 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 = .;
|
||||
}
|
||||
32
examples/LinkCard_demo/#loader/localize.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
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": "WAITING FOR GAME",
|
||||
"MSG_SCAN_CARD": "PLEASE SCAN YOUR CARD",
|
||||
"MSG_TRANSFERRING": "TRANSFERRING",
|
||||
"MSG_CARD_SENT": "CARD SENT",
|
||||
"MSG_ERROR": "ERROR",
|
||||
"MSG_PRESS_B_CANCEL": "PRESS B TO CANCEL",
|
||||
"MSG_NUMBERS": "0123456789"
|
||||
};
|
||||
*/
|
||||
|
||||
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('');
|
||||
}
|
||||
25
examples/LinkCard_demo/#loader/src/def.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#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
|
||||
160
examples/LinkCard_demo/#loader/src/erapi.h
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#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
|
||||
118
examples/LinkCard_demo/#loader/src/link.h
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
#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
|
||||
290
examples/LinkCard_demo/#loader/src/main.c
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
#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
|
||||
/* "0123456789" */
|
||||
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;
|
||||
}
|
||||
43
examples/LinkCard_demo/#loader/src/protocol.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#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
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
1
examples/LinkCard_demo/#testcards/testcard.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
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
|
||||
293
examples/LinkCard_demo/Makefile
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
#
|
||||
# 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
|
||||
BIN
examples/LinkCard_demo/gbfs_files/jap.loader
Normal file
BIN
examples/LinkCard_demo/gbfs_files/japeng.loader
Normal file
BIN
examples/LinkCard_demo/gbfs_files/usa.loader
Normal file
128
examples/LinkCard_demo/src/main.cpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// (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;
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -1,30 +1,21 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkCube.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
void log(std::string text);
|
||||
void wait(u32 verticalLines);
|
||||
bool didPress(unsigned short key, bool& pressed);
|
||||
inline void VBLANK() {}
|
||||
|
||||
bool a = false, b = false, l = false;
|
||||
bool a = true, b = true, l = true;
|
||||
|
||||
// (1) Create a LinkCube instance
|
||||
LinkCube* linkCube = new LinkCube();
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
|
||||
// (2) Add the interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_CUBE_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_add(INTR_VBLANK, []() {});
|
||||
interrupt_add(INTR_SERIAL, LINK_CUBE_ISR_SERIAL);
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -41,7 +32,7 @@ int main() {
|
|||
while (true) {
|
||||
// Title
|
||||
std::string output =
|
||||
"LinkCube_demo (v7.0.1)" + std::string(reset ? " !RESET!" : "") +
|
||||
"LinkCube_demo (v8.0.3)" + std::string(reset ? " !RESET!" : "") +
|
||||
"\n\nPress A to send\nPress B to clear\n (L = "
|
||||
"+1024)\n\nLast sent: " +
|
||||
std::to_string(counter) +
|
||||
|
|
@ -49,13 +40,13 @@ int main() {
|
|||
")\n\nReceived:\n" + received;
|
||||
|
||||
// (4) Send 32-bit values
|
||||
if (didPress(KEY_A, a)) {
|
||||
if (Common::didPress(KEY_A, a)) {
|
||||
counter++;
|
||||
linkCube->send(counter);
|
||||
}
|
||||
|
||||
// +1024
|
||||
if (didPress(KEY_L, l)) {
|
||||
if (Common::didPress(KEY_L, l)) {
|
||||
counter += 1024;
|
||||
linkCube->send(counter);
|
||||
}
|
||||
|
|
@ -66,7 +57,7 @@ int main() {
|
|||
}
|
||||
|
||||
// Clear
|
||||
if (didPress(KEY_B, b))
|
||||
if (Common::didPress(KEY_B, b))
|
||||
received = "";
|
||||
|
||||
// Reset warning
|
||||
|
|
@ -83,38 +74,8 @@ int main() {
|
|||
|
||||
// Print
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -1,22 +1,26 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkGPIO.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
void log(std::string text);
|
||||
std::string mode(std::string name, LinkGPIO::Pin pin);
|
||||
std::string value(std::string name, LinkGPIO::Pin pin, bool isHigh);
|
||||
std::string value(std::string name, LinkGPIO::Pin pin);
|
||||
|
||||
// (1) Create a LinkGPIO instance
|
||||
LinkGPIO* linkGPIO = new LinkGPIO();
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
static vu32 irqCount = 0;
|
||||
void SERIAL() {
|
||||
irqCount++;
|
||||
}
|
||||
|
||||
irq_init(NULL);
|
||||
irq_add(II_VBLANK, NULL);
|
||||
void init() {
|
||||
Common::initTTE();
|
||||
|
||||
interrupt_init();
|
||||
interrupt_add(INTR_VBLANK, []() {});
|
||||
interrupt_add(INTR_SERIAL, SERIAL);
|
||||
|
||||
// (2) Initialize the library
|
||||
linkGPIO->reset();
|
||||
|
|
@ -25,83 +29,95 @@ void init() {
|
|||
int main() {
|
||||
init();
|
||||
|
||||
bool left = true, up = true, right = true, down = true;
|
||||
bool start = true, select = true;
|
||||
|
||||
while (true) {
|
||||
// (3) Use the pins
|
||||
std::string output = "LinkGPIO_demo (v7.0.1)\n\n";
|
||||
std::string output = "LinkGPIO_demo (v8.0.3)\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;
|
||||
|
||||
// Modes
|
||||
// (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
|
||||
output += mode("SI", LinkGPIO::Pin::SI);
|
||||
output += mode("SO", LinkGPIO::Pin::SO);
|
||||
output += mode("SD", LinkGPIO::Pin::SD);
|
||||
output += mode("SC", LinkGPIO::Pin::SC);
|
||||
|
||||
// Separator
|
||||
// Print separator
|
||||
output += "\n---\n\n";
|
||||
|
||||
// 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 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);
|
||||
|
||||
// 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);
|
||||
// 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 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);
|
||||
output +=
|
||||
"\n\n---\nUse the D-PAD to change modes\nUse the buttons to set "
|
||||
"values\nUse STA/SEL to toggle SI IRQ";
|
||||
|
||||
// Print
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::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, bool isHigh) {
|
||||
std::string value(std::string name, LinkGPIO::Pin pin) {
|
||||
auto title = name + ": ";
|
||||
|
||||
return linkGPIO->getMode(pin) == LinkGPIO::Direction::INPUT
|
||||
? "< " + title + std::to_string(linkGPIO->readPin(pin)) + "\n"
|
||||
: "> " + title + std::to_string(isHigh) + "\n";
|
||||
// (4) Read pins
|
||||
return (linkGPIO->getMode(pin) == LinkGPIO::Direction::INPUT ? "< " : "> ") +
|
||||
title + std::to_string(linkGPIO->readPin(pin)) + "\n";
|
||||
}
|
||||
|
|
|
|||
289
examples/LinkIR_demo/Makefile
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
#
|
||||
# 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
|
||||
173
examples/LinkIR_demo/src/main.cpp
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// (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);
|
||||
}
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include "main.h"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <functional>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
|
|
@ -25,9 +24,9 @@ std::string outgoingData = "";
|
|||
u32 counter = 0;
|
||||
u32 frameCounter = 0;
|
||||
|
||||
bool left = false, right = false, up = false, down = false;
|
||||
bool a = false, b = false, l = false, r = false;
|
||||
bool start = false, select = false;
|
||||
bool left = true, right = true, up = true, down = true;
|
||||
bool a = true, b = true, l = true, r = true;
|
||||
bool start = true, select = true;
|
||||
std::string selectedNumber = "";
|
||||
std::string selectedPassword = "";
|
||||
std::string selectedDomain = "";
|
||||
|
|
@ -35,15 +34,14 @@ std::string selectedDomain = "";
|
|||
LinkMobile* linkMobile = nullptr;
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
start:
|
||||
log("LinkMobile_demo (v7.0.1)\n\nPress A to start");
|
||||
log("LinkMobile_demo (v8.0.3)\n\nPress A to start");
|
||||
waitForA();
|
||||
|
||||
// (1) Create a LinkMobile instance
|
||||
|
|
@ -51,12 +49,9 @@ start:
|
|||
|
||||
// (2) Add the required interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, LINK_MOBILE_ISR_VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_MOBILE_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_set_handler(INTR_TIMER3, LINK_MOBILE_ISR_TIMER);
|
||||
interrupt_enable(INTR_TIMER3);
|
||||
interrupt_add(INTR_VBLANK, LINK_MOBILE_ISR_VBLANK);
|
||||
interrupt_add(INTR_SERIAL, LINK_MOBILE_ISR_SERIAL);
|
||||
interrupt_add(INTR_TIMER3, LINK_MOBILE_ISR_TIMER);
|
||||
|
||||
// (3) Initialize the library
|
||||
linkMobile->activate();
|
||||
|
|
@ -77,7 +72,7 @@ start:
|
|||
}
|
||||
|
||||
// SELECT = stop
|
||||
if (didPress(KEY_SELECT, select)) {
|
||||
if (Common::didPress(KEY_SELECT, select)) {
|
||||
bool didShutdown = linkMobile->getState() == LinkMobile::State::SHUTDOWN;
|
||||
|
||||
if (hasError || didShutdown) {
|
||||
|
|
@ -90,7 +85,7 @@ start:
|
|||
|
||||
if (!didShutdown) {
|
||||
log("Waiting...");
|
||||
wait(228 * 60 * 3);
|
||||
Link::wait(228 * 60 * 3);
|
||||
}
|
||||
|
||||
goto start;
|
||||
|
|
@ -103,13 +98,13 @@ start:
|
|||
switch (linkMobile->getState()) {
|
||||
case LinkMobile::State::SESSION_ACTIVE: {
|
||||
// L = Read Configuration
|
||||
if (didPress(KEY_L, l)) {
|
||||
if (Common::didPress(KEY_L, l)) {
|
||||
readConfiguration();
|
||||
waitForA();
|
||||
}
|
||||
|
||||
// R = Call someone
|
||||
if (didPress(KEY_R, r)) {
|
||||
if (Common::didPress(KEY_R, r)) {
|
||||
std::string number = getNumberInput();
|
||||
if (number != "") {
|
||||
// (4) Call someone
|
||||
|
|
@ -118,7 +113,7 @@ start:
|
|||
}
|
||||
|
||||
// START = Call the ISP
|
||||
if (didPress(KEY_START, start)) {
|
||||
if (Common::didPress(KEY_START, start)) {
|
||||
std::string password = getPasswordInput();
|
||||
if (password != "") {
|
||||
// (7) Connect to the internet
|
||||
|
|
@ -129,7 +124,7 @@ start:
|
|||
}
|
||||
case LinkMobile::State::CALL_ESTABLISHED: {
|
||||
// L = hang up
|
||||
if (didPress(KEY_L, l)) {
|
||||
if (Common::didPress(KEY_L, l)) {
|
||||
// (6) Hang up
|
||||
linkMobile->hangUp();
|
||||
}
|
||||
|
|
@ -137,7 +132,7 @@ start:
|
|||
}
|
||||
case LinkMobile::State::PPP_ACTIVE: {
|
||||
// A = DNS query
|
||||
if (didPress(KEY_A, a) && !waitingDNS) {
|
||||
if (Common::didPress(KEY_A, a) && !waitingDNS) {
|
||||
std::string domain = getDomainInput();
|
||||
if (domain != "") {
|
||||
// (8) Run DNS queries
|
||||
|
|
@ -146,7 +141,7 @@ start:
|
|||
}
|
||||
}
|
||||
// L = hang up
|
||||
if (didPress(KEY_L, l)) {
|
||||
if (Common::didPress(KEY_L, l)) {
|
||||
// (6) Hang up
|
||||
linkMobile->hangUp();
|
||||
}
|
||||
|
|
@ -170,10 +165,16 @@ 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;
|
||||
|
|
@ -193,7 +194,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) {
|
||||
|
|
@ -243,7 +244,7 @@ void handlePPP() {
|
|||
log("Downloading... (" + std::to_string(chunk) + ", " +
|
||||
std::to_string(retry) + ")\n (hold START = close conn)\n\n" + output);
|
||||
|
||||
if (didPress(KEY_START, start)) {
|
||||
if (Common::didPress(KEY_START, start)) {
|
||||
log("Closing...");
|
||||
LinkMobile::CloseConn closeConn;
|
||||
linkMobile->closeConnection(
|
||||
|
|
@ -418,43 +419,43 @@ std::string getInput(std::string& field,
|
|||
std::string output = "Type " + inputName + ":\n\n";
|
||||
output += ">> " + field + "\n\n";
|
||||
|
||||
if (didPress(KEY_RIGHT, right)) {
|
||||
if (Common::didPress(KEY_RIGHT, right)) {
|
||||
selectedX++;
|
||||
if (selectedX >= (int)renderRows[0].size())
|
||||
selectedX = renderRows[0].size() - 1;
|
||||
}
|
||||
if (didPress(KEY_LEFT, left)) {
|
||||
if (Common::didPress(KEY_LEFT, left)) {
|
||||
selectedX--;
|
||||
if (selectedX < 0)
|
||||
selectedX = 0;
|
||||
}
|
||||
if (didPress(KEY_UP, up)) {
|
||||
if (Common::didPress(KEY_UP, up)) {
|
||||
selectedY--;
|
||||
if (selectedY < 0)
|
||||
selectedY = 0;
|
||||
}
|
||||
if (didPress(KEY_DOWN, down)) {
|
||||
if (Common::didPress(KEY_DOWN, down)) {
|
||||
selectedY++;
|
||||
if (selectedY >= (int)renderRows.size())
|
||||
selectedY = renderRows.size() - 1;
|
||||
}
|
||||
if (didPress(KEY_B, b)) {
|
||||
if (Common::didPress(KEY_B, b)) {
|
||||
if (field.size() > 0)
|
||||
field = field.substr(0, field.size() - 1);
|
||||
else
|
||||
return "";
|
||||
}
|
||||
if (didPress(KEY_A, a)) {
|
||||
if (Common::didPress(KEY_A, a)) {
|
||||
if (field.size() < maxChars)
|
||||
field += renderRows[selectedY][selectedX];
|
||||
}
|
||||
if (didPress(KEY_SELECT, select)) {
|
||||
if (Common::didPress(KEY_SELECT, select)) {
|
||||
field = defaultValues[selectedDefaultValue].value;
|
||||
selectedDefaultValue = (selectedDefaultValue + 1) % defaultValues.size();
|
||||
}
|
||||
if (didPress(KEY_START, start))
|
||||
if (Common::didPress(KEY_START, start))
|
||||
return field;
|
||||
if (altName != "" && didPress(KEY_L, l))
|
||||
if (altName != "" && Common::didPress(KEY_L, l))
|
||||
altActive = !altActive;
|
||||
|
||||
for (int y = 0; y < (int)renderRows.size(); y++) {
|
||||
|
|
@ -583,9 +584,7 @@ std::string getResultString(LinkMobile::CommandResult cmdResult) {
|
|||
void log(std::string text) {
|
||||
if (linkMobile != nullptr)
|
||||
VBlankIntrWait();
|
||||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
Common::log(text);
|
||||
}
|
||||
|
||||
std::string toStr(char* chars, int size) {
|
||||
|
|
@ -596,32 +595,8 @@ 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 (!didPress(KEY_A, a))
|
||||
while (!Common::didPress(KEY_A, a))
|
||||
;
|
||||
}
|
||||
|
||||
|
|
@ -630,6 +605,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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@
|
|||
|
||||
#include "../../../lib/LinkMobile.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "../../_lib/common.h"
|
||||
|
||||
struct DefaultValue {
|
||||
std::string name;
|
||||
|
|
@ -26,11 +25,11 @@ std::string getNumberInput();
|
|||
std::string getPasswordInput();
|
||||
std::string getDomainInput();
|
||||
std::string getTextInput(std::string& field,
|
||||
unsigned int maxChars,
|
||||
u32 maxChars,
|
||||
std::string inputName,
|
||||
std::vector<DefaultValue> defaultValues);
|
||||
std::string getInput(std::string& field,
|
||||
unsigned int maxChars,
|
||||
u32 maxChars,
|
||||
std::string inputName,
|
||||
std::vector<std::vector<std::string>> rows,
|
||||
std::vector<std::vector<std::string>> altRows,
|
||||
|
|
@ -44,8 +43,6 @@ 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>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
// (0) Include the header
|
||||
#include "../../../lib/LinkPS2Keyboard.hpp"
|
||||
|
||||
void log(std::string text);
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
static std::string scanCodes = "";
|
||||
static std::string output = "";
|
||||
static u32 irqs = 0;
|
||||
inline void VBLANK() {}
|
||||
static vu32 irqCount = 0;
|
||||
void SERIAL() {
|
||||
LINK_PS2_KEYBOARD_ISR_SERIAL();
|
||||
irqs++;
|
||||
irqCount++;
|
||||
}
|
||||
|
||||
// (1) Create a LinkPS2Keyboard instance
|
||||
|
|
@ -22,22 +19,19 @@ LinkPS2Keyboard* linkPS2Keyboard = new LinkPS2Keyboard([](u8 scanCode) {
|
|||
});
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
|
||||
// (2) Add the interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_add(INTR_VBLANK, []() {});
|
||||
interrupt_add(INTR_SERIAL, SERIAL);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkPS2Keyboard_demo (v7.0.1)\n\n";
|
||||
std::string output = "LinkPS2Keyboard_demo (v8.0.3)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkPS2Keyboard->isActive()) {
|
||||
|
|
@ -47,7 +41,7 @@ int main() {
|
|||
|
||||
if (keys & KEY_A) {
|
||||
// (3) Initialize the library
|
||||
log("Waiting...");
|
||||
Common::log("Waiting...");
|
||||
linkPS2Keyboard->activate();
|
||||
VBlankIntrWait();
|
||||
continue;
|
||||
|
|
@ -55,22 +49,16 @@ int main() {
|
|||
} else {
|
||||
if (keys & KEY_B) {
|
||||
scanCodes = "";
|
||||
irqs = 0;
|
||||
irqCount = 0;
|
||||
}
|
||||
output += std::to_string(irqs) + " - " + scanCodes;
|
||||
output += std::to_string(irqCount) + " - " + scanCodes;
|
||||
}
|
||||
|
||||
// Print
|
||||
VBlankIntrWait();
|
||||
LINK_PS2_KEYBOARD_ISR_VBLANK();
|
||||
log(output);
|
||||
Common::log(output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log(std::string text) {
|
||||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -1,43 +1,30 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkPS2Mouse.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/common.h"
|
||||
#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() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
|
||||
// (2) Add the interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_TIMER2, TIMER);
|
||||
interrupt_enable(INTR_TIMER2);
|
||||
interrupt_add(INTR_VBLANK, []() {});
|
||||
interrupt_add(INTR_TIMER2, []() {});
|
||||
|
||||
// Interrupt to handle B event (to reset)
|
||||
// B = SoftReset
|
||||
REG_KEYCNT = 0b10 | (1 << 14);
|
||||
interrupt_set_handler(INTR_KEYPAD, KEYPAD);
|
||||
interrupt_enable(INTR_KEYPAD);
|
||||
interrupt_add(INTR_KEYPAD, Common::ISR_reset);
|
||||
}
|
||||
|
||||
int main() {
|
||||
init();
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkPS2Mouse_demo (v7.0.1)\n\n";
|
||||
std::string output = "LinkPS2Mouse_demo (v8.0.3)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkPS2Mouse->isActive()) {
|
||||
|
|
@ -47,7 +34,7 @@ int main() {
|
|||
|
||||
if (keys & KEY_A) {
|
||||
// (3) Initialize the library
|
||||
log("Waiting...");
|
||||
Common::log("Waiting...");
|
||||
linkPS2Mouse->activate();
|
||||
VBlankIntrWait();
|
||||
continue;
|
||||
|
|
@ -62,14 +49,8 @@ int main() {
|
|||
|
||||
// Print
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::log(output);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log(std::string text) {
|
||||
tte_erase_screen();
|
||||
tte_write("#{P:0,0}");
|
||||
tte_write(text.c_str());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -1,27 +1,19 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkRawCable.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/common.h"
|
||||
#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() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
|
||||
// (2) Add the interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_RAW_CABLE_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_add(INTR_VBLANK, []() {});
|
||||
interrupt_add(INTR_SERIAL, LINK_RAW_CABLE_ISR_SERIAL);
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -33,7 +25,7 @@ int main() {
|
|||
u16 prevKeys = 0;
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkRawCable_demo (v7.0.1)\n\n";
|
||||
std::string output = "LinkRawCable_demo (v8.0.3)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkRawCable->isActive()) {
|
||||
|
|
@ -55,7 +47,7 @@ int main() {
|
|||
output +=
|
||||
"isReady() = " + std::to_string(linkRawCable->isReady()) + "\n\n";
|
||||
if (firstTransfer) {
|
||||
log(output + "Waiting...");
|
||||
Common::log(output + "Waiting...");
|
||||
firstTransfer = false;
|
||||
}
|
||||
|
||||
|
|
@ -63,20 +55,20 @@ int main() {
|
|||
// (4)/(5) Exchange 16-bit data with the connected consoles
|
||||
if (prevKeys == 0 && keys != 0 && linkRawCable->isReady()) {
|
||||
counter++;
|
||||
log(output + "...");
|
||||
Common::log(output + "...");
|
||||
LinkRawCable::Response response =
|
||||
linkRawCable->transfer(counter, []() {
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
return (keys & KEY_L) && (keys & KEY_R);
|
||||
});
|
||||
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);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
// (6) Exchange data asynchronously
|
||||
|
|
@ -84,17 +76,17 @@ int main() {
|
|||
linkRawCable->getAsyncState() == LinkRawCable::AsyncState::IDLE) {
|
||||
counter++;
|
||||
linkRawCable->transferAsync(counter);
|
||||
log(output + ">> " + std::to_string(counter));
|
||||
wait(228 * 60);
|
||||
Common::log(output + ">> " + std::to_string(counter));
|
||||
Link::wait(228 * 60);
|
||||
}
|
||||
if (linkRawCable->getAsyncState() == LinkRawCable::AsyncState::READY) {
|
||||
LinkRawCable::Response response = linkRawCable->getAsyncData();
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,27 +100,9 @@ int main() {
|
|||
|
||||
// Print
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,9 +123,8 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba -lgba-sprite-engine
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib \
|
||||
src/scenes \
|
||||
src/utils
|
||||
SRCDIRS := src ../_lib ../_lib/libgba-sprite-engine ../../lib ../../lib/iwram_code \
|
||||
src/scenes
|
||||
DATADIRS :=
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba $(PWD)/../_lib/libgba-sprite-engine
|
||||
|
|
@ -166,8 +165,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
#define LINK_RAW_WIRELESS_ENABLE_LOGGING
|
||||
|
||||
#include "../../../lib/LinkRawWireless.hpp"
|
||||
|
||||
#include <libgba-sprite-engine/gba_engine.h>
|
||||
#include <tonc.h>
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/interrupt.h"
|
||||
#include "scenes/DebugScene.h"
|
||||
#include "utils/SceneUtils.h"
|
||||
|
||||
void setUpInterrupts();
|
||||
void printTutorial();
|
||||
|
|
@ -27,19 +28,15 @@ int main() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
inline void ISR_reset() {
|
||||
RegisterRamReset(RESET_REG | RESET_VRAM);
|
||||
SoftReset();
|
||||
}
|
||||
|
||||
inline void setUpInterrupts() {
|
||||
interrupt_init();
|
||||
|
||||
interrupt_set_handler(INTR_VBLANK, [] {});
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_add(INTR_VBLANK, [] {});
|
||||
interrupt_add(INTR_SERIAL, LINK_RAW_WIRELESS_ISR_SERIAL);
|
||||
|
||||
// A+B+START+SELECT
|
||||
// A+B+START+SELECT = SoftReset
|
||||
#if MULTIBOOT_BUILD == 0
|
||||
REG_KEYCNT = 0b1100000000001111;
|
||||
interrupt_set_handler(INTR_KEYPAD, ISR_reset);
|
||||
interrupt_enable(INTR_KEYPAD);
|
||||
interrupt_add(INTR_KEYPAD, Common::ISR_reset);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 "utils/InputHandler.h"
|
||||
#include "utils/SceneUtils.h"
|
||||
#include "../../../_lib/common.h"
|
||||
#include "../../../_lib/libgba-sprite-engine/scene.h"
|
||||
|
||||
DebugScene::DebugScene(std::shared_ptr<GBAEngine> engine) : Scene(engine) {}
|
||||
|
||||
|
|
@ -28,6 +28,8 @@ 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;
|
||||
|
|
@ -112,12 +114,8 @@ void log(std::string string) {
|
|||
scrollPageDown();
|
||||
}
|
||||
|
||||
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> toArray(
|
||||
std::vector<u32> vector) {
|
||||
std::array<u32, LINK_RAW_WIRELESS_MAX_COMMAND_TRANSFER_LENGTH> array;
|
||||
for (u32 i = 0; i < vector.size(); i++)
|
||||
array[i] = vector[i];
|
||||
return array;
|
||||
const u32* toArray(std::vector<u32> vector) {
|
||||
return vector.data();
|
||||
}
|
||||
|
||||
std::vector<Background*> DebugScene::backgrounds() {
|
||||
|
|
@ -139,10 +137,11 @@ void DebugScene::load() {
|
|||
};
|
||||
|
||||
log("---");
|
||||
log("LinkRawWireless demo");
|
||||
log(" (v7.0.1)");
|
||||
log("LinkRawWireless_demo");
|
||||
log(" (v8.0.3)");
|
||||
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");
|
||||
|
|
@ -214,19 +213,19 @@ void DebugScene::addCommandMenuOptions() {
|
|||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x19 (StartHost)", .command = 0x19});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x1a (AcceptConnections)", .command = 0x1a});
|
||||
CommandMenuOption{.name = "0x1A (PollConnections)", .command = 0x1A});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x1b (EndHost)", .command = 0x1b});
|
||||
CommandMenuOption{.name = "0x1B (EndHost)", .command = 0x1B});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x1c (BroadcastRead1)", .command = 0x1c});
|
||||
CommandMenuOption{.name = "0x1C (BroadcastReadStart)", .command = 0x1C});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x1d (BroadcastRead2)", .command = 0x1d});
|
||||
CommandMenuOption{.name = "0x1D (BroadcastReadPoll)", .command = 0x1D});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x1e (BroadcastRead3)", .command = 0x1e});
|
||||
CommandMenuOption{.name = "0x1E (BroadcastReadEnd)", .command = 0x1E});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x1f (Connect)", .command = 0x1f});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x20 (IsFinishedConnect)", .command = 0x20});
|
||||
CommandMenuOption{.name = "0x1F (Connect)", .command = 0x1F});
|
||||
commandMenuOptions.push_back(CommandMenuOption{
|
||||
.name = "0x20 (IsConnectionComplete)", .command = 0x20});
|
||||
commandMenuOptions.push_back(
|
||||
CommandMenuOption{.name = "0x21 (FinishConnection)", .command = 0x21});
|
||||
commandMenuOptions.push_back(
|
||||
|
|
@ -254,7 +253,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) {
|
||||
|
|
@ -266,6 +265,7 @@ 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,6 +314,9 @@ void DebugScene::processButtons() {
|
|||
|
||||
if (startHandler->hasBeenPressedNow())
|
||||
resetAdapter();
|
||||
|
||||
if (rightHandler->hasBeenPressedNow())
|
||||
restoreExistingConnection();
|
||||
}
|
||||
|
||||
void DebugScene::toggleLogLevel() {
|
||||
|
|
@ -426,9 +429,9 @@ again2:
|
|||
if (byte3 == -1)
|
||||
goto again2;
|
||||
|
||||
u16 numberLow = linkRawWireless->buildU16((u8)byte1, (u8)byte0);
|
||||
u16 numberHigh = linkRawWireless->buildU16((u8)byte3, (u8)byte2);
|
||||
u32 number = linkRawWireless->buildU32(numberHigh, numberLow);
|
||||
u16 numberLow = Link::buildU16((u8)byte1, (u8)byte0);
|
||||
u16 numberHigh = Link::buildU16((u8)byte3, (u8)byte2);
|
||||
u32 number = Link::buildU32(numberHigh, numberLow);
|
||||
if (selectOption(">> 0x" + linkRawWireless->toHex(number, 8) + "?",
|
||||
std::vector<std::string>{"yes", "no"}) == 1)
|
||||
goto again0;
|
||||
|
|
@ -445,7 +448,7 @@ again:
|
|||
if (msB == -1)
|
||||
goto again;
|
||||
|
||||
u16 number = linkRawWireless->buildU16((u8)msB, (u8)lsB);
|
||||
u16 number = Link::buildU16((u8)msB, (u8)lsB);
|
||||
if (selectOption(">> 0x" + linkRawWireless->toHex(number, 4) + "?",
|
||||
std::vector<std::string>{"yes", "no"}) == 1)
|
||||
goto again;
|
||||
|
|
@ -471,9 +474,37 @@ 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;
|
||||
|
|
@ -551,10 +582,10 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
|
|||
return logOperation("sending " + name,
|
||||
[]() { return linkRawWireless->startHost(); });
|
||||
}
|
||||
case 0x1a: {
|
||||
case 0x1A: {
|
||||
return logOperation("sending " + name, []() {
|
||||
LinkRawWireless::AcceptConnectionsResponse response;
|
||||
bool success = linkRawWireless->acceptConnections(response);
|
||||
LinkRawWireless::PollConnectionsResponse response;
|
||||
bool success = linkRawWireless->pollConnections(response);
|
||||
|
||||
if (success) {
|
||||
for (u32 i = 0; i < response.connectedClientsSize; i++) {
|
||||
|
|
@ -569,9 +600,9 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
|
|||
return success;
|
||||
});
|
||||
}
|
||||
case 0x1b: {
|
||||
case 0x1B: {
|
||||
return logOperation("sending " + name, []() {
|
||||
LinkRawWireless::AcceptConnectionsResponse response;
|
||||
LinkRawWireless::PollConnectionsResponse response;
|
||||
bool success = linkRawWireless->endHost(response);
|
||||
|
||||
if (success) {
|
||||
|
|
@ -587,17 +618,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);
|
||||
|
|
@ -620,25 +651,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;
|
||||
|
|
@ -660,9 +691,13 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
|
|||
bool success = linkRawWireless->keepConnecting(response);
|
||||
|
||||
if (success) {
|
||||
log(std::string("< [phase] ") + (response.phase == 0 ? "CONNECTING"
|
||||
: response.phase == 1 ? "ERROR"
|
||||
: "SUCCESS"));
|
||||
log(std::string("< [phase] ") +
|
||||
(response.phase ==
|
||||
LinkRawWireless::ConnectionPhase::STILL_CONNECTING
|
||||
? "CONNECTING"
|
||||
: response.phase == LinkRawWireless::ConnectionPhase::ERROR
|
||||
? "ERROR"
|
||||
: "SUCCESS"));
|
||||
if (response.phase == LinkRawWireless::ConnectionPhase::SUCCESS)
|
||||
log("< [slot] " + std::to_string(response.assignedClientNumber));
|
||||
|
||||
|
|
@ -691,24 +726,56 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
|
|||
auto data = selectDataToSend();
|
||||
if (data.empty())
|
||||
return;
|
||||
u32 bytes = data[0];
|
||||
data.erase(data.begin());
|
||||
|
||||
return logOperation("sending " + name, [&data, bytes]() {
|
||||
LinkRawWireless::RemoteCommand remoteCommand;
|
||||
bool success = linkRawWireless->sendDataAndWait(
|
||||
toArray(data), data.size(), remoteCommand, bytes);
|
||||
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);
|
||||
|
||||
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]));
|
||||
bool success = linkRawWireless->sendCommandAsync(0x25, toArray(data),
|
||||
data.size(), true);
|
||||
if (!success) {
|
||||
log("! not now");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
});
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
case 0x26: {
|
||||
return logOperation("sending " + name, []() {
|
||||
|
|
@ -732,21 +799,39 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
|
|||
}
|
||||
case 0x27: {
|
||||
return logOperation("sending " + name, []() {
|
||||
LinkRawWireless::RemoteCommand remoteCommand;
|
||||
LinkRawWireless::CommandResult remoteCommand;
|
||||
bool success = linkRawWireless->wait(remoteCommand);
|
||||
|
||||
if (success) {
|
||||
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
|
||||
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
|
||||
for (u32 i = 0; i < remoteCommand.dataSize; i++) {
|
||||
log("< [param" + std::to_string(i) + "] " +
|
||||
linkRawWireless->toHex(remoteCommand.params[i]));
|
||||
linkRawWireless->toHex(remoteCommand.data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
});
|
||||
}
|
||||
case 0x30:
|
||||
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 0x32:
|
||||
case 0x33:
|
||||
case 0x34:
|
||||
|
|
@ -757,7 +842,7 @@ void DebugScene::processCommand(u32 selectedCommandIndex) {
|
|||
case 0x38:
|
||||
case 0x39:
|
||||
goto generic;
|
||||
case 0x3d:
|
||||
case 0x3D:
|
||||
goto simple;
|
||||
default:
|
||||
return;
|
||||
|
|
@ -810,13 +895,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 linkRawWireless->buildU16(qran_range(0, 256), qran_range(0, 256));
|
||||
return Link::buildU16(qran_range(0, 256), qran_range(0, 256));
|
||||
}
|
||||
default: {
|
||||
return selectU16();
|
||||
|
|
@ -848,6 +933,10 @@ 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) + "/" +
|
||||
|
|
@ -898,9 +987,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.responsesSize; i++) {
|
||||
for (u32 i = 0; i < result.dataSize; i++) {
|
||||
log("< [response" + std::to_string(i) + "] " +
|
||||
linkRawWireless->toHex(result.responses[i]));
|
||||
linkRawWireless->toHex(result.data[i]));
|
||||
}
|
||||
|
||||
if (!result.success)
|
||||
|
|
@ -908,14 +997,14 @@ void DebugScene::logGenericWaitCommand(std::string name, u32 id) {
|
|||
|
||||
log("Now WAITING...");
|
||||
|
||||
LinkRawWireless::RemoteCommand remoteCommand =
|
||||
LinkRawWireless::CommandResult remoteCommand =
|
||||
linkRawWireless->receiveCommandFromAdapter();
|
||||
|
||||
if (remoteCommand.success) {
|
||||
log("< [notif] " + linkRawWireless->toHex(remoteCommand.commandId));
|
||||
for (u32 i = 0; i < remoteCommand.paramsSize; i++) {
|
||||
for (u32 i = 0; i < remoteCommand.dataSize; i++) {
|
||||
log("< [param" + std::to_string(i) + "] " +
|
||||
linkRawWireless->toHex(remoteCommand.params[i]));
|
||||
linkRawWireless->toHex(remoteCommand.data[i]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -934,9 +1023,9 @@ void DebugScene::logSimpleCommand(std::string name,
|
|||
logOperation("sending " + name, [id, ¶ms]() {
|
||||
auto result =
|
||||
linkRawWireless->sendCommand(id, toArray(params), params.size());
|
||||
for (u32 i = 0; i < result.responsesSize; i++) {
|
||||
for (u32 i = 0; i < result.dataSize; i++) {
|
||||
log("< [response" + std::to_string(i) + "] " +
|
||||
linkRawWireless->toHex(result.responses[i]));
|
||||
linkRawWireless->toHex(result.data[i]));
|
||||
}
|
||||
return result.success;
|
||||
});
|
||||
|
|
@ -954,3 +1043,8 @@ void DebugScene::resetAdapter() {
|
|||
logOperation("resetting adapter",
|
||||
[]() { return linkRawWireless->activate(); });
|
||||
}
|
||||
|
||||
void DebugScene::restoreExistingConnection() {
|
||||
logOperation("restoring from multiboot",
|
||||
[]() { return linkRawWireless->restoreExistingConnection(); });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ 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
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
#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
|
||||
|
|
@ -134,7 +134,7 @@ TITLE := $(PROJ)
|
|||
LIBS := -ltonc -lugba
|
||||
|
||||
BUILD := build
|
||||
SRCDIRS := src ../_lib ../../lib
|
||||
SRCDIRS := src ../_lib ../../lib ../../lib/iwram_code
|
||||
DATADIRS := data
|
||||
INCDIRS := src
|
||||
LIBDIRS := $(TONCLIB) $(PWD)/../_lib/libugba
|
||||
|
|
@ -176,8 +176,12 @@ 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 ? ---
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ uint8_t transferByte(uint8_t value) {
|
|||
}
|
||||
|
||||
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 = transferByte(a);
|
||||
b = transferByte(b);
|
||||
|
|
@ -36,7 +36,7 @@ uint32_t transfer(uint32_t value) {
|
|||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t received = transfer(0xaabbccdd); // (2864434397)
|
||||
uint32_t received = transfer(0xAABBCCDD); // (2864434397)
|
||||
|
||||
Serial.println(received);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,21 @@
|
|||
// (0) Include the header
|
||||
#include "../../../lib/LinkSPI.hpp"
|
||||
|
||||
#include <tonc.h>
|
||||
#include <string>
|
||||
#include "../../_lib/common.h"
|
||||
#include "../../_lib/interrupt.h"
|
||||
|
||||
void log(std::string text);
|
||||
void wait(u32 verticalLines);
|
||||
inline void VBLANK() {}
|
||||
|
||||
// (1) Create a LinkSPI instance
|
||||
LinkSPI* linkSPI = new LinkSPI();
|
||||
|
||||
void init() {
|
||||
REG_DISPCNT = DCNT_MODE0 | DCNT_BG0;
|
||||
tte_init_se_default(0, BG_CBB(0) | BG_SBB(31));
|
||||
Common::initTTE();
|
||||
|
||||
// (2) Add the interrupt service routines
|
||||
interrupt_init();
|
||||
interrupt_set_handler(INTR_VBLANK, VBLANK);
|
||||
interrupt_enable(INTR_VBLANK);
|
||||
interrupt_set_handler(INTR_SERIAL, LINK_SPI_ISR_SERIAL);
|
||||
interrupt_enable(INTR_SERIAL);
|
||||
interrupt_add(INTR_VBLANK, VBLANK);
|
||||
interrupt_add(INTR_SERIAL, LINK_SPI_ISR_SERIAL);
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
|
@ -32,25 +26,27 @@ int main() {
|
|||
u32 counter = 0;
|
||||
|
||||
while (true) {
|
||||
std::string output = "LinkSPI_demo (v7.0.1)\n\n";
|
||||
std::string output = "LinkSPI_demo (v8.0.3)\n\n";
|
||||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
|
||||
if (!linkSPI->isActive()) {
|
||||
firstTransfer = true;
|
||||
output += "START: Set as Master\n";
|
||||
output += "START: Set as Master 256Kbps\n";
|
||||
output += "SELECT: Set as Slave\n";
|
||||
output += "RIGHT: Set as Master 2Mbps\n";
|
||||
output += "\n(stop: press L+R)\n";
|
||||
output += "(hold A on start for async)\n";
|
||||
output += "(hold B on start for waitMode)\n";
|
||||
output += "(hold UP for 8-bit mode)\n";
|
||||
output +=
|
||||
"\n\n\n\n\n\n\n\n\n[!] to test this demo...\n "
|
||||
"\n\n\n\n\n\n\n\n[!] to test this demo...\n "
|
||||
"...use a GBC Link Cable!";
|
||||
|
||||
if ((keys & KEY_START) | (keys & KEY_SELECT)) {
|
||||
if ((keys & KEY_START) | (keys & KEY_SELECT) | (keys & KEY_RIGHT)) {
|
||||
// (3) Initialize the library
|
||||
linkSPI->activate((keys & KEY_START) ? LinkSPI::Mode::MASTER_256KBPS
|
||||
: LinkSPI::Mode::SLAVE,
|
||||
linkSPI->activate((keys & KEY_START) ? LinkSPI::Mode::MASTER_256KBPS
|
||||
: (keys & KEY_SELECT) ? LinkSPI::Mode::SLAVE
|
||||
: LinkSPI::Mode::MASTER_2MBPS,
|
||||
(keys & KEY_UP) ? LinkSPI::DataSize::SIZE_8BIT
|
||||
: LinkSPI::DataSize::SIZE_32BIT);
|
||||
linkSPI->setWaitModeActive(keys &
|
||||
|
|
@ -64,7 +60,7 @@ int main() {
|
|||
linkSPI->getMode() == LinkSPI::Mode::SLAVE ? "[slave]" : "[master]";
|
||||
output += std::string(modeName) + "\n";
|
||||
if (firstTransfer) {
|
||||
log(output + "Waiting...");
|
||||
Common::log(output + "Waiting...");
|
||||
firstTransfer = false;
|
||||
}
|
||||
|
||||
|
|
@ -85,12 +81,12 @@ int main() {
|
|||
u16 keys = ~REG_KEYS & KEY_ANY;
|
||||
return (keys & KEY_L) && (keys & KEY_R);
|
||||
});
|
||||
log(output + ">> " + std::to_string(counter));
|
||||
wait(228 * 60);
|
||||
Common::log(output + ">> " + std::to_string(counter));
|
||||
Link::wait(228 * 60);
|
||||
}
|
||||
if (linkSPI->getAsyncState() == LinkSPI::AsyncState::READY) {
|
||||
log(output + "<< " + std::to_string(linkSPI->getAsyncData()));
|
||||
wait(228 * 60);
|
||||
Common::log(output + "<< " + std::to_string(linkSPI->getAsyncData()));
|
||||
Link::wait(228 * 60);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,26 +100,8 @@ int main() {
|
|||
|
||||
// Print
|
||||
VBlankIntrWait();
|
||||
log(output);
|
||||
Common::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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||