Compare commits

...

366 Commits

Author SHA1 Message Date
Rodrigo Alfonso
01770d7398 Merge pull request #24 from elliotsayes/rfu
Updating wireless adapter documentation
2025-08-29 00:51:50 -03:00
Elliot Sayes
f71706cc33 Reword for readability and make it clear that the checksum and other broadcast bytes are not checked/validated by hardware 2025-08-28 09:07:09 +02:00
Elliot Sayes
7e8aca5daa Update info 2025-08-28 03:07:03 +02:00
Rodrigo Alfonso
a7babd4ec2 Adding LinkWireless::isWirelessMultibootRom() 2025-08-18 06:33:50 -03:00
Rodrigo Alfonso
983411ea68 Adding (native | docker) option to ./compile.sh 2025-07-28 05:28:44 -03:00
Rodrigo Alfonso
6e5646e96b Fixing GCC 15 build 2025-07-28 05:28:00 -03:00
Rodrigo Alfonso
9447cdfff3 README: Explaining how to compile examples with Docker 2025-07-28 04:53:48 -03:00
Rodrigo Alfonso
a9359ade1e Removing dead code 2025-04-21 04:17:13 -03:00
Rodrigo Alfonso
6b94479c95 Updating DLC Loader 2025-04-21 03:55:47 -03:00
Rodrigo Alfonso
a8ab944b58 Improving reliability of card transfers 2025-04-21 03:46:51 -03:00
Rodrigo Alfonso
3d4b5f934d Supporting English language in Japanese DLC loader 2025-04-21 00:52:46 -03:00
Rodrigo Alfonso
d8908ca004 Version => v8.0.3 2025-04-19 08:22:33 -03:00
Rodrigo Alfonso
1ac74f4cf1 Fixing last glitch with e-Reader scan errors 2025-04-19 08:22:05 -03:00
Rodrigo Alfonso
0ebadc401f Updating readme 2025-03-21 14:18:03 -03:00
Rodrigo Alfonso
9af6dde347 Cleaning up e-reader loader code 2025-03-18 23:45:57 -03:00
Rodrigo Alfonso
5e212a0a81 Also checking EREADER_SIO_END 2025-03-18 20:42:55 -03:00
Rodrigo Alfonso
25831307a4 Fixing card loader trim script 2025-03-18 20:27:01 -03:00
Rodrigo Alfonso
6edef9ab20 Version => v8.0.2 2025-03-18 20:14:35 -03:00
Rodrigo Alfonso
5b20e96321 Improving LinkCard loader error handling 2025-03-18 20:14:12 -03:00
Rodrigo Alfonso
8fe9c5f710 Version => v8.0.1 2025-02-24 03:08:08 -03:00
Rodrigo Alfonso
9fc2d8af06 Filtering messages from hacked servers with invalid player IDs 2025-02-24 03:06:38 -03:00
Rodrigo Alfonso
1534677446
Merge pull request #23 from afska/v8.0.0
💳 LinkCard, 📺 LinkIR, and more 
2025-02-19 22:05:25 -03:00
Rodrigo Alfonso
e553ad67f0 Adding dependency graph 2025-02-19 09:21:48 -03:00
Rodrigo Alfonso
a37e6fd517 FIX: Wrong documentation 2025-02-19 08:24:25 -03:00
Rodrigo Alfonso
9e05647244 Removing warning about v8.0.0 not being ready; it's fairly ready 2025-02-19 08:19:48 -03:00
Rodrigo Alfonso
2336bfeb2d Reducing LinkUniversal initial wait 2025-02-19 07:43:55 -03:00
Rodrigo Alfonso
ab590d90e5 FIX: Making adapter login more close to spec 2025-02-18 23:57:44 -03:00
Rodrigo Alfonso
8d20770e2c LinkRawWireless//LinkSPI: Save a bit of IWRAM by removing unneeded checks in irq handler 2025-02-18 19:02:06 -03:00
Rodrigo Alfonso
4a91763451 Adding isConnectedNow() 2025-02-18 10:43:29 -03:00
Rodrigo Alfonso
bf386778e6 before => between, to* 2025-02-18 09:15:29 -03:00
Rodrigo Alfonso
dcbdf37250 FIX: LinkRawWireless enables the library before calling any action 2025-02-18 08:18:37 -03:00
Rodrigo Alfonso
6868ca18cd FIX: sendCommand shouldn't check isEnabled 2025-02-18 07:53:55 -03:00
Rodrigo Alfonso
d7c81f65c3 Updating butano license 2025-02-17 22:54:02 -03:00
Rodrigo Alfonso
ee70be4cf7 Adding note about cards 2025-02-17 22:46:11 -03:00
Rodrigo Alfonso
350000da5f Fixing new gcc warning 2025-02-17 22:11:26 -03:00
Rodrigo Alfonso
8b8daf9152 Renaming gba-link-universal-test 2025-02-17 22:08:43 -03:00
Rodrigo Alfonso
dc5fa75dc1 Adding gba-hpp and libtonc 2025-02-17 22:06:52 -03:00
Rodrigo Alfonso
729a77866a Adding open-source libraries section 2025-02-17 22:00:25 -03:00
Rodrigo Alfonso
ea515ab1d4 Fixing LinkCard tutorial 2025-02-17 21:40:31 -03:00
Rodrigo Alfonso
a10d4c6f65 Updating protocol.h 2025-02-17 21:33:43 -03:00
Rodrigo Alfonso
b620818178 Updating C bindings and adding C_LinkCard 2025-02-17 21:25:44 -03:00
Rodrigo Alfonso
3a19e21a15 Adding LinkCard to README 2025-02-17 20:54:28 -03:00
Rodrigo Alfonso
4a8c769feb Documenting LinkCard and transfer protocol 2025-02-17 20:28:39 -03:00
Rodrigo Alfonso
6e93e8650f FIX: Making reception more reliable. Tested against the official thing 2025-02-17 19:27:15 -03:00
Rodrigo Alfonso
f2cbf9906d FIX: Making loader more reliable 2025-02-17 18:35:29 -03:00
Rodrigo Alfonso
25238c4ef4 Fixing japanese 2025-02-17 10:05:13 -03:00
Rodrigo Alfonso
948bbef979 Updating loaders and fixing transfer 2025-02-17 09:48:28 -03:00
Rodrigo Alfonso
7b6071116a Fixing transfer 2025-02-17 09:38:23 -03:00
Rodrigo Alfonso
15f8ada24e FIX: mGBA was getting stuck during SIO mode changes 2025-02-17 08:31:25 -03:00
Rodrigo Alfonso
4f4631754e FIX: Receive works! Needs refactors 2025-02-17 07:43:09 -03:00
Rodrigo Alfonso
918d279eee FIX: DLC Loader now syncs data with game 2025-02-17 07:22:30 -03:00
Rodrigo Alfonso
9d4b61157a Adding experimental compatibility with official loaders 2025-02-17 06:47:43 -03:00
Rodrigo Alfonso
6f1908c648 FIX: Transfer errors 2025-02-17 06:27:36 -03:00
Rodrigo Alfonso
15ce27ef33 FIX: ScopeGuard usage 2025-02-17 05:12:28 -03:00
Rodrigo Alfonso
78e6fab8f2 Adding reset mechanism 2025-02-17 04:39:32 -03:00
Rodrigo Alfonso
18f9afecce Fixing issues with getConnectedDevice() 2025-02-17 02:52:51 -03:00
Rodrigo Alfonso
3645ca5d9f Updating loader binaries 2025-02-17 02:27:18 -03:00
Rodrigo Alfonso
237472ee15 Finishing card reception 2025-02-17 01:45:19 -03:00
Rodrigo Alfonso
6b44e02278 Clearing buffer every loop 2025-02-17 01:31:21 -03:00
Rodrigo Alfonso
5a97399b8e DLC Loader: cancel protocol 2025-02-17 01:07:07 -03:00
Rodrigo Alfonso
406ae0031f Implementing first part of card reception 2025-02-17 00:56:53 -03:00
Rodrigo Alfonso
5cb96b5b69 Adding cancellation callbacks to LinkCard and sending the right loader based on region 2025-02-17 00:08:47 -03:00
Rodrigo Alfonso
ec14b25b37 Adding tutorial on how to create BMP 2025-02-16 23:41:17 -03:00
Rodrigo Alfonso
c6ca9c563b Validating loader files 2025-02-16 09:01:03 -03:00
Rodrigo Alfonso
25fff2de3a Adding DLC Loader code 2025-02-16 08:55:01 -03:00
Rodrigo Alfonso
d95368060e Adding e-Reader notes 2025-02-16 08:32:37 -03:00
Rodrigo Alfonso
4b79f8d7f5 Adding 4-e license 2025-02-16 08:03:12 -03:00
Rodrigo Alfonso
afedb826ff Adding jap.loader 2025-02-16 08:00:57 -03:00
Rodrigo Alfonso
fd39aaf65e Adding USA DLC loader 2025-02-16 07:51:54 -03:00
Rodrigo Alfonso
115fb2ade2 Ordering LinkRawCable methods 2025-02-16 07:51:41 -03:00
Rodrigo Alfonso
bce0671881 Making it work with both adapters (USA and JAP) 2025-02-16 00:26:19 -03:00
Rodrigo Alfonso
5c0cf89008 Unhardcoding e-Reader checksum 2025-02-15 22:17:53 -03:00
Rodrigo Alfonso
86eed10480 FIX: Adding pre-transfer wait to fix it on hardware 2025-02-15 10:16:33 -03:00
Rodrigo Alfonso
97028c5231 Cleaning up the previous mess 2025-02-15 09:18:46 -03:00
Rodrigo Alfonso
b4393f03c0 WIP - Sending loader with hardcoded checksum // works 2025-02-15 08:03:42 -03:00
Rodrigo Alfonso
ee50d4f731 Main loader sending part done. Now I think it needs the CRC 2025-02-15 04:18:25 -03:00
Rodrigo Alfonso
3d0fa72083 Adding code to call sendLoader in LinkCard example 2025-02-15 03:44:31 -03:00
Rodrigo Alfonso
5372106db5 Adding LinkCard boilerplate code and device detection 2025-02-15 00:49:57 -03:00
Rodrigo Alfonso
724d21e75d LinkRawCable: default to max baud rate 2025-02-14 23:50:30 -03:00
Rodrigo Alfonso
bb2d417f3f Organizing licenses 2025-02-14 06:50:12 -03:00
Rodrigo Alfonso
37a8ad487b Adding LinkCard_demo example 2025-02-14 06:30:53 -03:00
Rodrigo Alfonso
38f40894f7 Adding explicit include in common.h 2025-02-14 06:30:38 -03:00
Rodrigo Alfonso
0845b39136 Using LinkRawCable static API 2025-02-14 06:01:14 -03:00
Rodrigo Alfonso
39e80d0f88 Moving setMultiPlayMode to public API 2025-02-14 05:58:12 -03:00
Rodrigo Alfonso
de7554bf45 Fixing LinkIR.cpp comment 2025-02-12 10:08:59 -03:00
Rodrigo Alfonso
0086f66ad3 Updating LinkGPIO gif 2025-02-12 01:20:44 -03:00
Rodrigo Alfonso
799e1aee00 Updating UART gif 2025-02-12 01:09:47 -03:00
Rodrigo Alfonso
219c14ef07 Adding LinkIR_demo and missing LinkCube_demo to compile script 2025-02-11 23:50:42 -03:00
Rodrigo Alfonso
534378032a Adding video and fixing docs 2025-02-11 23:40:35 -03:00
Rodrigo Alfonso
8b1de89179 Adding LinkIR C bindings 2025-02-11 23:02:01 -03:00
Rodrigo Alfonso
4f664002ba Adding IR adapter README 2025-02-11 22:43:20 -03:00
Rodrigo Alfonso
5b5846aade FIX: IR adapter detection 2025-02-11 22:00:35 -03:00
Rodrigo Alfonso
8abb3d7ae4 Removing monitor 2025-02-11 21:01:46 -03:00
Rodrigo Alfonso
c4d9c4a2c5 FIX: Making IR demodulation more reliable using DEMODULATION_HYSTERESIS_DELAY 2025-02-11 20:58:51 -03:00
Rodrigo Alfonso
1becbcfbb8 Fixing issues with detection 2025-02-11 20:25:05 -03:00
Rodrigo Alfonso
ad7775740b Rethinking demodulation code using interrupts 2025-02-11 08:40:47 -03:00
Rodrigo Alfonso
965e927e3a Stop estimating transition time 2025-02-11 02:13:25 -03:00
Rodrigo Alfonso
c26e06e0b7 Adding key test 2025-02-11 01:33:48 -03:00
Rodrigo Alfonso
d05b3513e8 Making receiveNEC more reliable 2025-02-11 00:41:42 -03:00
Rodrigo Alfonso
4b51164964 Improving IR example 2025-02-11 00:23:19 -03:00
Rodrigo Alfonso
296674fbee Receiving NEC signals 2025-02-10 23:25:20 -03:00
Rodrigo Alfonso
546937d338 Sending startTimeout first 2025-02-10 23:14:59 -03:00
Rodrigo Alfonso
30b9ff2ece Moving notes to main hpp 2025-02-10 23:01:23 -03:00
Rodrigo Alfonso
dbd0175159 Controlling how the library methods behave when not activated 2025-02-10 22:56:21 -03:00
Rodrigo Alfonso
57b94f0b4d Updating Makefiles 2025-02-10 22:56:05 -03:00
Rodrigo Alfonso
366d5f710b Organizing IR code 2025-02-10 21:23:37 -03:00
Rodrigo Alfonso
cf689a4708 Moving cpp files to iwram_code directory 2025-02-10 20:40:30 -03:00
Rodrigo Alfonso
42b41f84ff Demodulating signals 2025-02-10 20:27:11 -03:00
Rodrigo Alfonso
faccdac486 Trying to read signals 2025-02-10 07:18:17 -03:00
Rodrigo Alfonso
5aa625bf7c Exposing the LED 2025-02-10 03:01:32 -03:00
Rodrigo Alfonso
acd414f017 Adding infrared adapter docs 2025-02-10 02:18:10 -03:00
Rodrigo Alfonso
57e8fb5b7b Implementing things more close to spec 2025-02-10 02:13:20 -03:00
Rodrigo Alfonso
4a685f5d5f Adding code to measure microseconds 2025-02-10 02:01:45 -03:00
Rodrigo Alfonso
c0c2283cfb Adding sendNEC helper 2025-02-10 00:35:22 -03:00
Rodrigo Alfonso
a3ce3d76fc Sending a modulated NEC IR signal 2025-02-10 00:16:05 -03:00
Rodrigo Alfonso
a83ed0a6e3 Implementing IR adapter detection 2025-02-07 23:32:41 -03:00
Rodrigo Alfonso
5ad59ea2e1 Fixing wrong documentation message 2025-02-07 22:29:06 -03:00
Rodrigo Alfonso
e7574ccd14 Improving LinkGPIO example and adding LinkGPIO::getSIInterrupts() 2025-02-07 20:39:13 -03:00
Rodrigo Alfonso
97bb7ca405 FIX: Sanitizing adapters' byte count 2025-02-07 07:02:22 -03:00
Rodrigo Alfonso
db815e4749 Adding some observations from multiboot protocol 2025-02-07 02:59:38 -03:00
Rodrigo Alfonso
b39a79f526 Normalizing code style in multiboot libs 2025-02-07 02:52:01 -03:00
Rodrigo Alfonso
f5430c1700 Normalizing naming style 2025-02-06 08:18:26 -03:00
Rodrigo Alfonso
886f99f886 Renaming old constant 2025-02-06 08:02:05 -03:00
Rodrigo Alfonso
44ffc903d4 LinkWirelessMultiboot: Moving progress reporting to example (without needing to enable logs) 2025-02-06 08:01:09 -03:00
Rodrigo Alfonso
705ad0638d Adding more details to the multiboot protocol 2025-02-06 07:45:56 -03:00
Rodrigo Alfonso
10591d1d6b Stop clearing the whooole screen on every log! 2025-02-05 09:11:48 -03:00
Rodrigo Alfonso
8a71953275 Rollback previous commit 2025-02-05 03:30:29 -03:00
Rodrigo Alfonso
e9f6fdff80 Using -Os by default in ISR 2025-02-05 03:11:00 -03:00
Rodrigo Alfonso
9f8f4033be Adding missing attributes to timer functions 2025-02-05 03:06:20 -03:00
Rodrigo Alfonso
2957fa9bbe Setting LINK_WIRELESS_PUT_ISR_IN_IWRAM_TIMER=1 by default 2025-02-05 02:22:06 -03:00
Rodrigo Alfonso
3ca906cc4f Setting default wireless interval to 75 2025-02-05 02:08:54 -03:00
Rodrigo Alfonso
6055e4faac Organizing ISR functions 2025-02-05 01:54:23 -03:00
Rodrigo Alfonso
8e17f9a1a2 Fixing timer isr iwram macro 2025-02-04 22:14:58 -03:00
Rodrigo Alfonso
c1e1a167c4 Added control knobs for LINK_WIRELESS_PUT_ISR_IN_IWRAM 2025-02-04 10:27:01 -03:00
Rodrigo Alfonso
d88d765e24 Making LINK_WIRELESS_ENABLE_NESTED_IRQ independent from LINK_WIRELESS_PUT_ISR_IN_IWRAM 2025-02-04 09:27:02 -03:00
Rodrigo Alfonso
126fbd958c Dividing wireless profiler in two roms (IWRAM code and ROM code) 2025-02-04 09:22:18 -03:00
Rodrigo Alfonso
582d3b2ad0 LinkUniversal: Resetting mode to link cable after deactivate(...) 2025-02-04 08:06:03 -03:00
Rodrigo Alfonso
2c88e66852 Adding resetTimeout() method 2025-02-04 07:02:51 -03:00
Rodrigo Alfonso
e2e95f08ab FIX: Preventing gcc from reordering playerCount and currentPlayerId set 2025-02-04 06:18:21 -03:00
Rodrigo Alfonso
b7295b2443 Fixing default didPress(...) key handling 2025-02-04 05:51:36 -03:00
Rodrigo Alfonso
1124981c81 Logging forwarded count 2025-02-03 23:03:24 -03:00
Rodrigo Alfonso
c6b399f5b2 Now LinkRawWireless_demo can perform ghost sends 2025-02-03 21:47:57 -03:00
Rodrigo Alfonso
97adf6a8b0 Optimizations: Only iterate until playerCount, calculating min ACK only once 2025-02-03 10:21:08 -03:00
Rodrigo Alfonso
ba8e444d9b Moving more critical code to IWRAM and more non-critical code to ROM 2025-02-03 09:54:00 -03:00
Rodrigo Alfonso
6f2ec52e8d FIX: Overflow handling when forwarding messages 2025-02-03 08:58:14 -03:00
Rodrigo Alfonso
b88b0271df Inlining some helpers 2025-02-03 07:42:51 -03:00
Rodrigo Alfonso
5c2b395324 Cleaning up LinkWireless' new protocol changes 2025-02-03 07:06:58 -03:00
Rodrigo Alfonso
475f380fd0 WIP: New protocol (works!) 2025-02-03 04:31:40 -03:00
Rodrigo Alfonso
8972b62517 Adding canSend(...) method 2025-01-30 09:57:07 -03:00
Rodrigo Alfonso
9b0b08f368 Improving lagging button 2025-01-30 09:37:35 -03:00
Rodrigo Alfonso
20f24b3e60 Now the LinkCable_full example lag button only waits 5 frames 2025-01-30 09:33:03 -03:00
Rodrigo Alfonso
02a654dcb3 FIX: didQueueOverflow now also notifies failed forwarded messages 2025-01-30 09:29:46 -03:00
Rodrigo Alfonso
b0ab3f7fa4 FIX: Clear flag in LinkUniversal::didQueueOverflow 2025-01-30 09:12:20 -03:00
Rodrigo Alfonso
5c7f7d393c Adding didQueueOverflow alert 2025-01-30 08:59:13 -03:00
Rodrigo Alfonso
67455254db Adding useful logs to LinkUniversal_full 2025-01-30 08:45:23 -03:00
Rodrigo Alfonso
c0d5f15b5b Initializing these so state before activate() is consistent 2025-01-30 07:31:37 -03:00
Rodrigo Alfonso
9fb91309b6 playerCount() cannot be greater than LINK_UNIVERSAL_MAX_PLAYERS 2025-01-30 06:28:41 -03:00
Rodrigo Alfonso
eb8ce2d3fe Optimization: don't add initial data if playerCount doesn't require it 2025-01-30 06:13:26 -03:00
Rodrigo Alfonso
83e91deb87 Revert "Using getState()"
This reverts commit 137df23a74.
2025-01-30 06:05:25 -03:00
Rodrigo Alfonso
137df23a74 Using getState() 2025-01-30 05:51:59 -03:00
Rodrigo Alfonso
2c1e370bbe LinkRawWireless: Implementing command 0x30 DisconnectClient 2025-01-30 04:48:03 -03:00
Rodrigo Alfonso
ad85a77611 Fixing default value of localHeartbeat 2025-01-29 22:27:46 -03:00
Rodrigo Alfonso
d59bf93cd5 FIX: Failing to forward a message due to queues being full will also trigger queue overflow 2025-01-29 22:06:33 -03:00
Rodrigo Alfonso
a8382c91eb LinkWireless: Allow sending 0xFFFF, adding heartbeat to clients, fixing a memory access based on a network packet 2025-01-29 11:09:24 -03:00
Rodrigo Alfonso
ffae5ad1e5 README: Stop using fully qualified names in the Return type column 2025-01-28 08:30:40 -03:00
Rodrigo Alfonso
cb9c6e7fd2 Finishing polymorphism between LinkCableMultiboot::Async and LinkWirelessMultiboot::Async 2025-01-28 07:37:40 -03:00
Rodrigo Alfonso
cbfe0b7514 Making LinkWirelessMultiboot::Async faster with a timer, making it polymorphic with LinkCableMultiboot::Async 2025-01-28 07:01:03 -03:00
Rodrigo Alfonso
62583201b8 Fixing LinkPS2Mouse documentation 2025-01-28 04:50:46 -03:00
Rodrigo Alfonso
f5f1bde1fe FIX: When using LINK_WIRELESS_ENABLE_NESTED_IRQ, preventing acknowledge from being interrupted 2025-01-27 10:03:04 -03:00
Rodrigo Alfonso
25788dc238 Adding queue overflow flag to LinkWireless_demo 2025-01-27 08:45:42 -03:00
Rodrigo Alfonso
00bd94d71b Removing unused Overwrite generic parameter from Queue 2025-01-27 04:30:28 -03:00
Rodrigo Alfonso
705f071162 Fixing sendRom documentation 2025-01-27 04:13:17 -03:00
Rodrigo Alfonso
65841b3cb2 Replacing LINK_WIRELESS_MULTIBOOT_ASYNC_LIMIT_TRANSFER_SPEED with sendRom(...) parameter 2025-01-27 03:32:23 -03:00
Rodrigo Alfonso
c9397f095b Ensuring the wireless adapter always respond commands with less values than 30 2025-01-27 03:05:00 -03:00
Rodrigo Alfonso
d12e1ac4c7 FIX: Increasing CMD timeout for acknowledge 2025-01-27 02:33:52 -03:00
Rodrigo Alfonso
14d1cff22e Fixing issues with C bindings and enum classes 2025-01-26 23:47:04 -03:00
Rodrigo Alfonso
97ddfc7edc didQueueOverflow(...) shouldn't be [[nodiscard]] 2025-01-26 23:16:56 -03:00
Rodrigo Alfonso
a82088ad7c Converting enum to enum class 2025-01-26 23:13:24 -03:00
Rodrigo Alfonso
59f12cb329 Making handshake faster 2025-01-26 21:07:58 -03:00
Rodrigo Alfonso
dd1735fb76 LinkWirelessMultiboot: Implementing waitForReadySignal correctly 2025-01-26 21:05:41 -03:00
Rodrigo Alfonso
b2b7c0986f Updating C bindings 2025-01-26 10:34:50 -03:00
Rodrigo Alfonso
3c2a3c68bc Adding option LINK_WIRELESS_MULTIBOOT_ASYNC_LIMIT_TRANSFER_SPEED 2025-01-26 10:23:48 -03:00
Rodrigo Alfonso
b6c2d7bd1a More efficient pollConnections(...) in wireless multiboot async 2025-01-26 10:00:23 -03:00
Rodrigo Alfonso
12e41941c4 FIX: Making sendHost(...) more reliable 2025-01-26 09:33:22 -03:00
Rodrigo Alfonso
c376ae84bb Fixing exchangeNewData 2025-01-25 03:43:08 -03:00
Rodrigo Alfonso
8e43b5be25 Fixing and documenting LINK_WIRELESS_MULTIBOOT_ENABLE_LOGGING 2025-01-25 03:29:08 -03:00
Rodrigo Alfonso
a720b14fa5 Fixing warnings 2025-01-25 02:51:42 -03:00
Rodrigo Alfonso
e68df67aee Finishing LinkWirelessMultiboot::Async documentation 2025-01-25 02:20:13 -03:00
Rodrigo Alfonso
68f9c62c9a Fixing async multiboot C bindings 2025-01-25 01:51:46 -03:00
Rodrigo Alfonso
74ea6fcdc5 Fix handshake bug in async rewrite 2025-01-25 00:34:18 -03:00
Rodrigo Alfonso
f7ac879011 Fix sendAndExpectData overload 2025-01-25 00:28:11 -03:00
Rodrigo Alfonso
ac5f28ad4f Adding IRQ timeout 2025-01-24 23:49:16 -03:00
Rodrigo Alfonso
51aaf4c152 Starting LinkWirelessMultiboot::Async. It boots! Needs some adjustments 2025-01-24 23:44:30 -03:00
Rodrigo Alfonso
00d327b867 Fixing stop method and other stuff 2025-01-23 07:44:11 -03:00
Rodrigo Alfonso
64d948db25 Fixing LinkWirelessMultiboot docs 2025-01-23 01:17:08 -03:00
Rodrigo Alfonso
aa756f22f3 Fixing randomSeed initialization 2025-01-22 23:09:15 -03:00
Rodrigo Alfonso
e5ecf1affc Fixing type issues 2025-01-22 23:05:03 -03:00
Rodrigo Alfonso
d567eb3fdd Renaming AcceptConnections => PollConnections 2025-01-22 20:59:46 -03:00
Rodrigo Alfonso
c5a48a5187 Adding UNALIGNED result code 2025-01-22 11:35:41 -03:00
Rodrigo Alfonso
c82152068e Completing C bindings 2025-01-22 11:28:30 -03:00
Rodrigo Alfonso
f3b29cb835 Updating docs 2025-01-22 10:48:50 -03:00
Rodrigo Alfonso
55ba174de1 Adding LinkCableMultiboot::Async documentation 2025-01-22 09:49:49 -03:00
Rodrigo Alfonso
2a11159182 Making observedPlayers more stable 2025-01-22 09:02:33 -03:00
Rodrigo Alfonso
331032998b Adding option LINK_CABLE_MULTIBOOT_ASYNC_DISABLE_NESTED_IRQ 2025-01-22 08:39:40 -03:00
Rodrigo Alfonso
80bb0741c4 FIX: Disabling nested IRQs when sending from vblank isr 2025-01-22 08:12:58 -03:00
Rodrigo Alfonso
8fe104f43c Fixing mark ready flow 2025-01-22 07:50:01 -03:00
Rodrigo Alfonso
6bf0b3fe55 Adding clear parameter to getResult 2025-01-22 07:36:58 -03:00
Rodrigo Alfonso
535d8ce536 Adding mark ready signal 2025-01-22 07:25:04 -03:00
Rodrigo Alfonso
c76c8dc599 Adding LinkCableMultiboot::Async documentation 2025-01-22 07:06:01 -03:00
Rodrigo Alfonso
eceaa5d60b Fixing documentation 2025-01-22 06:22:27 -03:00
Rodrigo Alfonso
963dfb18a3 Using vu types 2025-01-22 06:10:09 -03:00
Rodrigo Alfonso
99dbe371ae LinkCable: Setting playerCount=1 as default (like LinkWireless) 2025-01-22 06:09:14 -03:00
Rodrigo Alfonso
d901058cf5 Enabling nested interrupts after reading the message to allow audio player switch buffers on time 2025-01-22 05:41:17 -03:00
Rodrigo Alfonso
79dbc4c9c1 Removing sync wait 2025-01-21 23:44:56 -03:00
Rodrigo Alfonso
d666be4c93 FIX: Correctly initializing clientData 2025-01-21 22:53:20 -03:00
Rodrigo Alfonso
8066c03641 Adding missing methods and fixes after initial test 2025-01-20 09:34:25 -03:00
Rodrigo Alfonso
805e6f58a4 Adding state timeout 2025-01-20 06:57:27 -03:00
Rodrigo Alfonso
555c9f67cc Fixing state and result system 2025-01-20 06:44:17 -03:00
Rodrigo Alfonso
c2b55a14d0 Finishing making cable multiboot code async. Needs validation / API adjustments 2025-01-20 06:12:26 -03:00
Rodrigo Alfonso
4da4c4996d Formatting code 2025-01-20 03:55:14 -03:00
Rodrigo Alfonso
21a2b63618 Moving half of the transfer process to the async handler 2025-01-19 08:23:03 -03:00
Rodrigo Alfonso
0d6ec2ea7c I think all clients must reply in the same exchange 2025-01-19 07:07:24 -03:00
Rodrigo Alfonso
03ebe92572 Adding last validations before SWI 2025-01-19 06:13:58 -03:00
Rodrigo Alfonso
7dc43d301e This struct wasn't being used 2025-01-19 03:21:14 -03:00
Rodrigo Alfonso
c2ad57cabe Validating final part of header transfer 2025-01-19 03:12:09 -03:00
Rodrigo Alfonso
572c584994 Moving _qran_range and randomSeed to Link:: common namespace 2025-01-19 02:56:16 -03:00
Rodrigo Alfonso
41fe4b00a2 Cleaning up code and adding new validations to sync code 2025-01-19 02:40:37 -03:00
Rodrigo Alfonso
bf3dd47e4b Wtf, removing public modifier 2025-01-18 09:15:47 -03:00
Rodrigo Alfonso
af05d42935 Improving sendRom documentation 2025-01-18 09:08:13 -03:00
Rodrigo Alfonso
5bed387c5d Adding WIP code for manual multiboot transfer (no SWI) 2025-01-17 08:46:25 -03:00
Rodrigo Alfonso
7b28dd3640 Fixing version tags 2025-01-17 00:26:03 -03:00
Rodrigo Alfonso
4564c2fb3c Revert "Removing s/r latch and making it the default behavior"
This reverts commit 52d23fdb54.
2025-01-16 22:45:12 -03:00
Rodrigo Alfonso
c356afc8ae Formatting code 2025-01-16 20:10:09 -03:00
Rodrigo Alfonso
f0017e828b Optimization: reduce msg count by only sending confirmations of online players 2025-01-16 19:49:05 -03:00
Rodrigo Alfonso
b9736e8cb3 Merge branch 'master' of https://github.com/afska/gba-link-connection into v8.0.0_reviewed 2025-01-16 10:42:16 -03:00
Rodrigo Alfonso
b93b55fc19 Version => v7.0.3 2025-01-16 10:37:58 -03:00
Rodrigo Alfonso
413809e792 FIX: Keep timer active here to avoid possible lockups when using LinkUniversal::waitFor(...) 2025-01-16 10:37:39 -03:00
Rodrigo Alfonso
52d23fdb54 Removing s/r latch and making it the default behavior 2025-01-16 09:40:32 -03:00
Rodrigo Alfonso
d0d43c797d Updating example layout 2025-01-16 08:38:31 -03:00
Rodrigo Alfonso
190dea598e Making internal signal level struct private and adding levels to LinkWireless_demo 2025-01-16 08:08:48 -03:00
Rodrigo Alfonso
1f298b5164 LinkWireless: Removing waits from start host 2025-01-16 07:57:52 -03:00
Rodrigo Alfonso
87748c6714 LinkWireless: Using SignalLevel (0x11) instead of AcceptConnections (0x1a) to update player count 2025-01-16 06:23:05 -03:00
Rodrigo Alfonso
7040f03087 Merge branch 'v8.0.0' of https://github.com/afska/gba-link-connection into v8.0.0 2025-01-16 01:58:55 -03:00
Rodrigo Alfonso
5e49c84903 LinkWireless: Improve timeout handling, so old confirmation numbers still trigger timeouts 2025-01-16 01:58:44 -03:00
Rodrigo Alfonso
411e98f7bd LinkUniversal: On deactivate, only call deactivate() on cable mode if it's active 2025-01-16 01:56:57 -03:00
Rodrigo Alfonso
fd5f298649 Using msgFlags[i] as a boolean 2025-01-15 10:05:22 -03:00
Rodrigo Alfonso
6d0ced0bbd FIX: Keep timer active if start() fails during activation. This can fix possible lockups when using LinkUniversal::waitFor(...) 2025-01-15 08:27:33 -03:00
Rodrigo Alfonso
7548e2ca9b Renaming profiler log 2025-01-15 07:18:00 -03:00
Rodrigo Alfonso
a840fdc792 Adding CPU usage metrics to LinkCable_stress 2025-01-15 06:52:04 -03:00
Rodrigo Alfonso
1ee4088568 Logging interval in LinkUniversal_stress example 2025-01-15 05:41:22 -03:00
Rodrigo Alfonso
f6b3204f32 Adding clear parameter and missing bindings 2025-01-15 01:32:26 -03:00
Rodrigo Alfonso
468e6e61c5 Renaming it to didQueueOverflow() 2025-01-15 01:21:51 -03:00
Rodrigo Alfonso
b5885f39cb Fix missing return true and other errors 2025-01-14 12:09:01 -03:00
Rodrigo Alfonso
b2e518cf3d Fixing LinkCube documentation 2025-01-14 11:44:17 -03:00
Rodrigo Alfonso
5eaa6cbe16 Handling queue overflow in LinkUniversal 2025-01-14 11:34:07 -03:00
Rodrigo Alfonso
30bd8a33cc Handling queue overflow in LinkCable 2025-01-14 11:26:46 -03:00
Rodrigo Alfonso
64c02b816d Renaming didInternalQueueOverflow => didReceiveQueueOverflow 2025-01-14 11:03:49 -03:00
Rodrigo Alfonso
de1a4a5c8f Handling queue overflow in LinkCube 2025-01-14 10:41:36 -03:00
Rodrigo Alfonso
4cda03b3ab Ah but external queue overflow was an illusion 2025-01-14 10:28:49 -03:00
Rodrigo Alfonso
47b95de178 Adding methods for handling queue overflow 2025-01-14 10:18:18 -03:00
Rodrigo Alfonso
d022fba681 Updating README and small fixes after reviewing the whole branch 2025-01-14 00:11:07 -03:00
Rodrigo Alfonso
08facca76b Documenting getSendDataHeaderFor(...) and getReceiveDataResponse(...) 2025-01-13 19:28:03 -03:00
Rodrigo Alfonso
5a74dd4e9b Removing standard library usage from C bindings 2025-01-13 10:09:39 -03:00
Rodrigo Alfonso
fa33580bfe Docs: Adding clarification about the 0x1c, 0x1d, 0x1e loop 2025-01-13 08:28:43 -03:00
Rodrigo Alfonso
0c3a3fcf6e Adding developer notes about nested irqs 2025-01-13 07:40:42 -03:00
Rodrigo Alfonso
8e0fabdd4d LinkWireless: marking some methods as inline 2025-01-13 07:08:25 -03:00
Rodrigo Alfonso
084b18284a LinkWireless: ISR optimizations, stop copying values! 2025-01-13 06:38:50 -03:00
Rodrigo Alfonso
996eb824d4 Using LINK_NOINLINE 2025-01-13 06:07:06 -03:00
Rodrigo Alfonso
c0b69c1fc1 LinkWirelessMultiboot: adding retries for final transfer 2025-01-13 05:13:33 -03:00
Rodrigo Alfonso
bc5cfab36e Simplifying LinkWireless::__onSerial() profiling code 2025-01-13 03:32:15 -03:00
Rodrigo Alfonso
f2d5f877ed Merge branch 'master' of https://github.com/afska/gba-link-connection into v8.0.0 2025-01-13 03:27:53 -03:00
Rodrigo Alfonso
4c2784aef7 Adjusting wireless profiler code 2025-01-13 03:13:16 -03:00
Rodrigo Alfonso
dd7e6f6eff Adjusting wireless profiler 2025-01-13 02:44:21 -03:00
Rodrigo Alfonso
200f33bd97 Adding signal levels to LinkUniversal_full example 2025-01-13 00:50:43 -03:00
Rodrigo Alfonso
8021b391a8 LinkWireless: Implementing getSignalLevel(...) 2025-01-13 00:19:49 -03:00
Rodrigo Alfonso
6b740f3616 LinkRawWireless: Implementing SignalLevel (0x11) command 2025-01-13 00:06:58 -03:00
Rodrigo Alfonso
5d8544b28f Renaming wireless cmd IsFinishedConnect => IsConnectionComplete 2025-01-12 23:45:41 -03:00
Rodrigo Alfonso
0516d71472 Sorting LinkRawWireless methods 2025-01-12 23:43:37 -03:00
Rodrigo Alfonso
f166dcb07d Reducing ISR code size when clock inversion is not needed 2025-01-12 21:11:24 -03:00
Rodrigo Alfonso
7027d219d3 Adding attributes LINK_INLINE and LINK_NOINLINE 2025-01-12 10:54:38 -03:00
Rodrigo Alfonso
10555a1573 Fixing issues with C bindings 2025-01-12 10:11:56 -03:00
Rodrigo Alfonso
6e8be79bd0 Version v7.1.0 => v8.0.0 2025-01-12 09:57:27 -03:00
Rodrigo Alfonso
532c178c02 Standarizing usage of libugba interrupt handler instead of tonc in examples and docs 2025-01-12 09:21:08 -03:00
Rodrigo Alfonso
d688241275 Removing unneeded constants 2025-01-12 09:00:35 -03:00
Rodrigo Alfonso
cfeda020db Providing default implementations for common string functions 2025-01-12 08:58:38 -03:00
Rodrigo Alfonso
0bfdb0b520 Removing std::array completely 2025-01-12 08:07:06 -03:00
Rodrigo Alfonso
bfa230f697 Extracting cstdio functions 2025-01-12 07:28:57 -03:00
Rodrigo Alfonso
b35a5f360c Helping the compiler figure out that LinkWireless doesn't need clock inversion code 2025-01-12 07:09:40 -03:00
Rodrigo Alfonso
b3ddf16b35 Extracting some macros to _link_common 2025-01-12 06:44:05 -03:00
Rodrigo Alfonso
3fb27158d1 Improving LinkUniversal::deactivate(...) documentation 2025-01-12 06:38:33 -03:00
Rodrigo Alfonso
1407a6e17b Using LinkRawWireless::getSendDataHeaderFor(...) instead of duplicating the logic 2025-01-12 06:35:12 -03:00
Rodrigo Alfonso
b93b3ce476 Adding LinkWireless::_setLogger(...) and LinkWirelessMultiboot::_setLogger(...) methods 2025-01-12 06:18:35 -03:00
Rodrigo Alfonso
76cc626d27 Moving async command system from LinkWireless to LinkRawWireless 2025-01-12 06:10:59 -03:00
Rodrigo Alfonso
79f783923e Disabling SoftReset in multiboot examples that use gba-sprite-engine 2025-01-12 06:09:21 -03:00
Rodrigo Alfonso
892f49bf79 LinkRawWireless: inverted ACK wait 2025-01-12 06:07:36 -03:00
Rodrigo Alfonso
4b29529f0a FIX: LinkWirelessMultiboot now confirms the last packet too, detecting failures at the end of the transfer 2025-01-12 06:03:25 -03:00
Rodrigo Alfonso
c2be671310 LinkRawWireless: Sorting private / public methods 2025-01-11 22:06:55 -03:00
Rodrigo Alfonso
4e3d2f9610 Merge branch 'master' of https://github.com/afska/gba-link-connection into v7.1.0 2025-01-11 22:06:35 -03:00
Rodrigo Alfonso
63958e12e7 Version => v7.0.2: Backported fixes planned for the next release 2025-01-11 21:46:43 -03:00
Rodrigo Alfonso
df66fd3064 FIX: LinkUniversal now initializes randomSeed 2025-01-11 21:43:38 -03:00
Rodrigo Alfonso
1bf1edda0f FIX: Uninitialized and unaligned reads in LinkWirelessOpenSDK 2025-01-11 21:43:29 -03:00
Rodrigo Alfonso
0990e424eb FIX: Wrong user name length validation 2025-01-11 21:40:00 -03:00
Rodrigo Alfonso
17d613366b FIX: Missing logs in LinkWirelessMultiboot example 2025-01-11 21:39:38 -03:00
Rodrigo Alfonso
ae1cf8170a Extracting some repeated functions to _link_common 2025-01-07 00:55:34 -03:00
Rodrigo Alfonso
aaf0201077 Profiler: using average to calculate time in ISRs 2025-01-06 11:04:04 -03:00
Rodrigo Alfonso
e4dc98bd0f Adding help to demo 2025-01-06 09:09:45 -03:00
Rodrigo Alfonso
7ed5640edf Now the interval can be changed in LinkWireless_demo 2025-01-06 08:53:06 -03:00
Rodrigo Alfonso
51eae9d80d Improving wireless profiler ROM 2025-01-06 07:02:45 -03:00
Rodrigo Alfonso
5cf4a138db Improving wireless profiler ROM 2025-01-06 06:20:56 -03:00
Rodrigo Alfonso
1130ffd81d Compile script fix: making backup part optional 2025-01-06 05:47:05 -03:00
Rodrigo Alfonso
7b6cf7f227 Improving profiling ROM, removing internal methods from C bindings 2025-01-06 05:45:51 -03:00
Rodrigo Alfonso
f672bd7c3e Revising sync() documentation, again 2025-01-05 09:36:20 -03:00
Rodrigo Alfonso
afb05da665 Stop duplicating int definitions 2025-01-05 08:52:59 -03:00
Rodrigo Alfonso
8cf3bb5a1c Adding volatile to variables that could change during interrupts and are read in main thread to prevent issues with compiler optimizations 2025-01-05 08:44:37 -03:00
Rodrigo Alfonso
2129c77377 FIX: Mutating linkSPI properly after refactor 2025-01-05 08:20:20 -03:00
Rodrigo Alfonso
ad29078090 Logging rejected messages in wireless profiler rom 2025-01-05 07:53:02 -03:00
Rodrigo Alfonso
7fc2b0d2c0 Stop using new / delete for internal instances, to allow using some libraries entirely on the stack 2025-01-05 07:01:32 -03:00
Rodrigo Alfonso
8ec893b0ff LinkCable: Refactoring to use LinkRawCable internally 2025-01-05 06:07:09 -03:00
Rodrigo Alfonso
107624e68f Improving wireless adapter deactivation in LinkUniversal and LinkWirelessMultiboot 2025-01-04 08:13:21 -03:00
Rodrigo Alfonso
30120f3bf3 Removing debug method from LinkWireless 2025-01-04 07:39:11 -03:00
Rodrigo Alfonso
cae4f588cd Checking config.maxPlayers when restoring connection 2025-01-04 06:54:43 -03:00
Rodrigo Alfonso
dc97649efb Renaming restoreFromMultiboot => restoreExistingConnection 2025-01-04 06:18:19 -03:00
Rodrigo Alfonso
f2506eef06 Refactor: Extracting repeated functions in examples to a common file 2025-01-04 03:20:42 -03:00
Rodrigo Alfonso
2fed290399 Fixing SoftReset in the multiboot version of the examples 2025-01-04 02:01:09 -03:00
Rodrigo Alfonso
6bfe90ed3e Adding 'restore from multiboot' to LinkUniversal 2025-01-04 01:31:34 -03:00
Rodrigo Alfonso
6743f2582a Adding 'restore from multiboot' to LinkWireless 2025-01-03 23:56:27 -03:00
Rodrigo Alfonso
b8643e0ddc Adding 'restore from multiboot' functionality to LinkRawWireless 2025-01-03 22:55:08 -03:00
Rodrigo Alfonso
9b41cf30df LinkRawWireless: Implementing SystemStatus command (0x13) 2025-01-03 12:25:10 -03:00
Rodrigo Alfonso
f9e0e1dd63 Documenting internal instances 2025-01-03 09:00:04 -03:00
Rodrigo Alfonso
a062a3acd6 Extracting event constants 2025-01-03 08:39:35 -03:00
Rodrigo Alfonso
9bc88c9dd5 LinkSPI_demo: Adding 2Mbps mode 2025-01-03 07:41:16 -03:00
Rodrigo Alfonso
6b9f5ba08e Centralizing usages of strlen and memcpy 2025-01-03 07:35:34 -03:00
Rodrigo Alfonso
8f9db769d4 Using std::memcpy instead of memcpy 2025-01-03 07:27:56 -03:00
Rodrigo Alfonso
a7ca676565 FIX: LinkUniversal now initializes randomSeed 2025-01-03 07:22:37 -03:00
Rodrigo Alfonso
aea3ae742a FIX: Uninitialized and unaligned reads in LinkWirelessOpenSDK 2025-01-03 06:56:57 -03:00
Rodrigo Alfonso
f7567b489e Documenting extracted methods 2025-01-02 19:17:20 -03:00
Rodrigo Alfonso
28b0ecda64 Moving most of the file transfer logic to LinkWirelessOpenSDK 2025-01-02 18:57:13 -03:00
Rodrigo Alfonso
283dd81b73 Removing unneeded finish(...) calls 2025-01-02 04:47:16 -03:00
Rodrigo Alfonso
05568ad4bf Moving some generic file transfer code from LinkWirelessMultiboot to LinkWirelessOpenSDK 2025-01-02 04:22:44 -03:00
Rodrigo Alfonso
9ea8f0c54b Removing unneeded waits in LinkWirelessMultiboot 2025-01-02 03:16:57 -03:00
Rodrigo Alfonso
be645a8f75 Moving wait(...) function to _link_common.hpp 2025-01-02 03:09:10 -03:00
Rodrigo Alfonso
898a5051d5 Removing unused code in LinkCube example 2025-01-02 02:58:21 -03:00
Rodrigo Alfonso
af5b9e3461 LinkWireless: Refactoring the whole thing to use LinkRawWireless internally 2025-01-02 02:02:59 -03:00
Rodrigo Alfonso
d43131a35c Adding missing param documentation 2025-01-01 23:37:40 -03:00
Rodrigo Alfonso
b1eb8c9c4e FIX: Wrong user name length validation 2025-01-01 22:43:02 -03:00
Rodrigo Alfonso
9e77e1010e Adding LinkWireless::isServerClosed 2025-01-01 20:21:11 -03:00
Rodrigo Alfonso
9af5aa2e1a Version => v7.1.0, updating license 2025-01-01 20:00:16 -03:00
Rodrigo Alfonso
4813ad2a34 FIX: Missing logs in LinkWirelessMultiboot example 2025-01-01 19:59:38 -03:00
Rodrigo Alfonso
e44fe5007e LinkWirelessMultiboot: Adding missing volatile flag, wait fix, and renaming cancel => listener 2025-01-01 19:59:23 -03:00
Rodrigo Alfonso
51f1c0ada3 Improving LinkMobile example: Now it hangs up when a p2p connection is closed 2024-12-29 06:05:58 -03:00
Rodrigo Alfonso
78cd7aa78b Using LinkGPIO::Pin::SD 2024-12-24 09:31:13 -03:00
Rodrigo Alfonso
4d2ab6c7ba Updating license year 2024-12-09 15:23:21 -03:00
Rodrigo Alfonso
22b413dd54 Also returning WRONG_STATE if the server is already closed 2024-12-08 03:32:26 -03:00
Rodrigo Alfonso
2e290a1a1d Removing noinline modifiers adding for debugging 2024-11-30 02:02:30 +00:00
Rodrigo Alfonso
33bdbc3810 Adding ready flag to LinkWirelessMultiboot to start transfer early 2024-11-30 01:48:26 +00:00
Rodrigo Alfonso
d645d47ba8 Adding LinkWireless::closeServer() 2024-11-29 06:33:43 +00:00
Rodrigo Alfonso
b2b1926d4a LinkRawWireless: adding missing bye() command 2024-11-29 05:52:19 +00:00
Rodrigo Alfonso
2628c2130e Adding missing methods in C bindings to update configuration in realtime 2024-11-29 05:44:09 +00:00
Rodrigo Alfonso
8121b7aa85 updateInterval() => resetTimer() 2024-11-29 05:09:28 +00:00
Rodrigo Alfonso
6754b7e058 Adding updateInterval() to LinkCable, LinkWireless, and LinkUniversal 2024-11-29 04:53:45 +00:00
Rodrigo Alfonso
29da94f0e0 Fixing typo 2024-11-28 21:41:32 +00:00
Rodrigo Alfonso
481381c9fb Ensuring a clean RCNT state when activating LinkWireless to prevent incompatibilities with LinkGPIO or other libraries 2024-11-22 20:16:30 +00:00
Rodrigo Alfonso
71c946f404 Separating main lib licenses from examples licenses 2024-10-24 17:38:23 +01:00
Rodrigo Alfonso
6ab48b40ff
Fix formatting 2024-10-09 20:46:27 -03:00
Rodrigo Alfonso
ea29c749fa Removing sed from compile script 2024-09-23 04:14:42 -03:00
Rodrigo Alfonso
39e5214785 Updating README 2024-09-14 06:17:24 -03:00
169 changed files with 12941 additions and 4989 deletions

View 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.

View 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.

View File

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

View 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

View 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

View 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.

View 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.

View 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.

View File

@ -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.

View File

@ -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/.

View 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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View 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. *
****************************************************************************/

View File

@ -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.

View 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.

View File

@ -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
View File

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

3
.prettierignore Normal file
View File

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

4
.prettierrc Normal file
View File

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

View File

@ -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

764
README.md

File diff suppressed because it is too large Load Diff

260
docs/e_reader.md Normal file
View 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.

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

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
docs/img/link-card.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 812 KiB

After

Width:  |  Height:  |  Size: 669 KiB

BIN
docs/img/link-ir.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 KiB

After

Width:  |  Height:  |  Size: 1023 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

172
docs/infrared_adapter.md Normal file
View 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.

View File

@ -13,15 +13,15 @@
The Mobile Adapter GB was an accessory designed to allow the Game Boy Color, and later the Game Boy Advance, to connect online via cellular networks in Japan. Released on January 27, 2001, it supported a limited number of games before service was shutdown on December 14, 2002. Many of the compatible games supported features such on mail clients, downloadable bonus content, player-versus-player modes, and even online tournaments. It represented Nintendo's first official attempt at online gaming for its handhelds.
* The Mobile Adapter is a small device that essentially allows a Japanese phone to connect to the Game Boy's link port
* Model number is CGB-005
* Officially released with 3 different versions of the Mobile Adapter. Each featured distinct colors to work with different type of phones
* Each Mobile Adapter came packaged with a cartridge called the Mobile Trainer to help configure and setup the device
* Servers were formally hosted at gameboy.datacenter.ne.jp
- The Mobile Adapter is a small device that essentially allows a Japanese phone to connect to the Game Boy's link port
- Model number is CGB-005
- Officially released with 3 different versions of the Mobile Adapter. Each featured distinct colors to work with different type of phones
- Each Mobile Adapter came packaged with a cartridge called the Mobile Trainer to help configure and setup the device
- Servers were formally hosted at gameboy.datacenter.ne.jp
Below, the Mobile Adapter variants are explained in further detail:
Blue -> Used to connect PDC phones.
Blue -> Used to connect PDC phones.
Yellow -> Used to connect cdmaOne phones.
Red -> Used to connect DDI phones.
Green -> Would have been used to connect PHS phones, but this version was never released.
@ -32,31 +32,31 @@ There are currently 22 known games that are compatible with the Mobile Adapter:
Game Boy Color : 6 Total
* Game Boy Wars 3
* Hello Kitty: Happy House
* Mobile Golf
* Mobile Trainer
* Net de Get Minigames @ 100
* Pocket Monsters Crystal Version
- Game Boy Wars 3
- Hello Kitty: Happy House
- Mobile Golf
- Mobile Trainer
- Net de Get Minigames @ 100
- Pocket Monsters Crystal Version
Game Boy Advance : 16 Total
* All-Japan GT Championship
* Daisenryaku For Game Boy Advance
* Doraemon: Midori no Wakusei Doki Doki Daikyuushuutsu!
* Exciting Bass
* EX Monopoly
* JGTO Licensed: Golfmaster Mobile
* Kinniku Banzuke ~Kongou-kun no Daibouken!~
* Mail de Cute
* Mario Kart Advance
* Mobile Pro Baseball: Control Baton
* Monster Guardians
* Morita Shougi Advance
* Napoleon
* Play Novel: Silent Hill
* Starcom: Star Communicator
* Zero-Tours
- All-Japan GT Championship
- Daisenryaku For Game Boy Advance
- Doraemon: Midori no Wakusei Doki Doki Daikyuushuutsu!
- Exciting Bass
- EX Monopoly
- JGTO Licensed: Golfmaster Mobile
- Kinniku Banzuke ~Kongou-kun no Daibouken!~
- Mail de Cute
- Mario Kart Advance
- Mobile Pro Baseball: Control Baton
- Monster Guardians
- Morita Shougi Advance
- Napoleon
- Play Novel: Silent Hill
- Starcom: Star Communicator
- Zero-Tours
Two games were planned but later cancelled: **beatmaniaGB Net Jam** for the GBC and **Horse Racing Creating Derby** for the GBA.
@ -68,7 +68,7 @@ On the GBC, the Mobile Adapter operates using the fastest available setting (64K
```
-------------------------------------------------
Section | Length
Section | Length
-------------------------------------------------
Magic Bytes : 0x99 0x66 | 2 bytes
Packet Header | 4 bytes
@ -96,7 +96,7 @@ Bytes 0-254 | Arbitrary data
-------------------------------------------------
Packet Checksum
Packet Checksum
-------------------------------------------------
Byte 1 | High byte of 16-bit sum
Byte 2 | Low byte of 16-bit sum
@ -144,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**

View File

@ -23,7 +23,7 @@ _You can make this screen display any game_
When I started, I used the following resources to start being able to talk with the wireless adapter:
- [This Gist contains some details](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
[![Image without alt text or caption](img/wireless/0x16.png)](img/wireless/0x16.png)
- Send length: 6, response length: 0
- The data to be broadcast out to all adapters. Examples of use include 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:
[![Image without alt text or caption](img/wireless/broadcast.png)](img/wireless/broadcast.png)
(if you read from right to left, it says `ICE CLIMBER` - `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`
[![Image without alt text or caption](img/wireless/0x1d.png)](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`
[![Image without alt text or caption](img/wireless/0x1f.png)](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`
[![Image without alt text or caption](img/wireless/0x20.png)](img/wireless/0x20.png)
@ -318,7 +329,7 @@ Both Pokemon games and the multiboot ROM that the adapter sends when no cartridg
[![Image without alt text or caption](img/wireless/0x21.png)](img/wireless/0x21.png)
- Send length: 0, response length: 1
- Called after [IsFinishedConnect](#isfinishedconnect---0x20), responds with the final device ID (which tends to be equal to the ID from the previous command), the `clientNumber` in bits 16 and 17, and if all went well, zeros in its remaining bits.
- 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
[![Image without alt text or caption](img/wireless/0x30.png)](img/wireless/0x30.png)
- Send length 1, reponse length: 0
- This command disconnects clients. The argument is a bitmask of the client ID to disconnect. Sending `0x1` means "disconnect client number 0", sending `0x2` means "disconnect client number 1", and sending `0xF` would disconnect all the clients. After disconnecting a client, its ID won't appear on `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 its ready_.
5. The adapter goes low when it's ready.
6. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
[![during clock inversion](img/wireless/ack-inverted.png)](img/wireless/ack-inverted.png)
1. The adapter goes low as soon as it can.
2. The GBA goes high.
3. The adapter goes high.
4. The GBA goes low _when its ready_, but **wait at least 40us**! (\*)
5. The adapter goes low when it's ready.
6. The adapter starts a transfer, clock starts pulsing, and both sides exchange the next 32 bit value.
> (\*) Clock inversion is _finicky_. If you don't wait enough time between transfers, the adapter will desync _forever_ (well, until you reset it with _SD=HIGH_). `LinkWireless` doesn't use wait commands and calls the regular `SendData` instead, it's less efficient but way more reliable.
## 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

View File

@ -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 ? ---

View File

@ -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;
}

View File

@ -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 ? ---

View File

@ -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());
}

View File

@ -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 ? ---

View File

@ -4,8 +4,8 @@
#include "main.h"
#include <libgba-sprite-engine/gba_engine.h>
#include "../../_lib/interrupt.h"
#include "../../_lib/libgba-sprite-engine/scene.h"
#include "scenes/TestScene.h"
#include "utils/SceneUtils.h"
void setUpInterrupts();
void printTutorial();
@ -40,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("");
}

View File

@ -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;

View File

@ -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
}

View File

@ -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

View File

@ -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 ? ---

View File

@ -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);
}

View File

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

View 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

View File

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

View 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

View 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
```

View 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

View 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 = .;
}

View 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": "  ",
"MSG_SCAN_CARD": "   ",
"MSG_TRANSFERRING": "",
"MSG_CARD_SENT": " ",
"MSG_ERROR": "",
"MSG_PRESS_B_CANCEL": "   ",
"MSG_NUMBERS": ""
};
*/
for (const key in data) {
const str = data[key];
const buf = iconv.encode(str, 'Shift_JIS');
const hexBytes = Array.from(buf).map(byte => '0x' + byte.toString(16).padStart(2, '0'));
hexBytes.push("0x00");
console.log(`/* "${str}" */`);
console.log(`const u8 ${key}[] = { ${hexBytes.join(', ')} };`);
console.log('');
}

View File

@ -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

View 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

View 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

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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View 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

View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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;
}

View File

@ -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 ? ---

View File

@ -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;
}

View File

@ -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 ? ---

View File

@ -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";
}

View 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

View 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);
}
}

View File

@ -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 ? ---

View File

@ -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;
}

View File

@ -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>

View File

@ -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 ? ---

View File

@ -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());
}

View File

@ -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 ? ---

View File

@ -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());
}

View File

@ -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 ? ---

View File

@ -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;
}
};
}

View File

@ -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 ? ---

View File

@ -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
}

View File

@ -1,14 +1,14 @@
#define LINK_RAW_WIRELESS_ENABLE_LOGGING
#include "../../../../lib/LinkRawWireless.hpp"
#include "DebugScene.h"
#include <libgba-sprite-engine/background/text_stream.h>
#include <tonc.h>
#include <algorithm>
#include <functional>
#include "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, &params]() {
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(); });
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ? ---

View File

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

View File

@ -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);
}

View File

@ -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;
}
};
}

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