├── .gitmodules ├── .gitignore ├── vendor └── README.md ├── LICENSE.md ├── src ├── pick.h ├── ui.h ├── link.h ├── link.c ├── ui.c ├── main.c └── pick.c ├── Makefile ├── README.md └── NOTES.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/libgbfs"] 2 | path = vendor/libgbfs 3 | url = https://github.com/dmdemoura/libgbfs 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # testing files 4 | /*.bin 5 | /*.gba 6 | /*.gbfs 7 | /*.raw 8 | /*.sav 9 | /*.sa2 10 | 11 | # build output 12 | /build 13 | 14 | -------------------------------------------------------------------------------- /vendor/README.md: -------------------------------------------------------------------------------- 1 | # Vendored dependencies 2 | 3 | ## libgbfs 4 | 5 | devkitPro ships with the GBFS tools, but not the GBFS library. I've included [Amanda Moura]'s libgbfs distribution here to build 4-e against. 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2024 Mattie Behrens. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | “Software”), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject 9 | to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 18 | ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/pick.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of 4-e . 3 | * 4 | * Copyright 2024 Mattie Behrens. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * “Software”), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject 12 | * to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 22 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef INCLUDE_PICK_H 27 | #define INCLUDE_PICK_H 28 | 29 | #include 30 | 31 | #include "gbfs.h" 32 | 33 | #define PICKER_PAGE_SIZE 16 34 | 35 | size_t pickBin(const GBFS_FILE *gbfs); 36 | 37 | #endif /* INCLUDE_PICK_H */ 38 | -------------------------------------------------------------------------------- /src/ui.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of 4-e . 3 | * 4 | * Copyright 2024 Mattie Behrens. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * “Software”), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject 12 | * to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 22 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef INCLUDE_UI_H 27 | #define INCLUDE_UI_H 28 | 29 | void initScreen(); 30 | void clearScreen(); 31 | void status(const char *message, const char *name); 32 | void done(const char *message, const char *name); 33 | 34 | #endif /* INCLUDE_UI_H */ 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(and $(strip $(DEVKITPRO)),$(strip $(DEVKITARM))),) 2 | $(error Make sure DEVKITPRO and DEVKITARM are correctly set in your environment.) 3 | endif 4 | 5 | # Name of your ROM 6 | # 7 | # Add _mb to the end to build a multiboot ROM. 8 | PROJECT := 4-e 9 | 10 | # Uncomment this if you're building a library 11 | # 12 | # BUILD_LIB := yes 13 | 14 | # Options for gbafix (optional) 15 | # 16 | # Title: 12 characters 17 | # Game code: 4 characters 18 | # Maker code: 2 characters 19 | # Version: 1 character 20 | ROM_TITLE := 21 | ROM_GAMECODE := 22 | ROM_MAKERCODE := 23 | ROM_VERSION := 24 | 25 | # 26 | # Files 27 | # 28 | # All options support glob patterns like `src/*.c`. 29 | # 30 | 31 | # Binary files to process with bin2s 32 | BINARY_FILES := 33 | 34 | # Audio files to process with mmutil 35 | AUDIO_FILES := 36 | 37 | # Graphics files to process with grit 38 | # 39 | # Every file requires an accompanying .grit file, 40 | # so gfx/test.png needs gfx/test.grit 41 | GRAPHICS := 42 | 43 | # Source files to compile 44 | SOURCES := src/link.c src/main.c src/pick.c src/ui.c \ 45 | vendor/libgbfs/source/libgbfs.c 46 | 47 | # Include directories 48 | INCLUDES := vendor/libgbfs/include 49 | 50 | # 51 | # Dependencies 52 | # 53 | 54 | # Library directories, with /include and /lib 55 | LIBDIRS := $(DEVKITPRO)/libgba $(DEVKITPRO)/libtonc 56 | 57 | # Libraries to link 58 | LIBS := tonc 59 | 60 | # 61 | # Directories 62 | # 63 | 64 | # All build output goes here 65 | BUILDDIR := build 66 | 67 | # 68 | # Build Options 69 | # 70 | 71 | # Compiler flags (all languages) 72 | ALLFLAGS := -Wall -Wextra -g3 -gdwarf-4 -O2 \ 73 | -ffunction-sections -fdata-sections \ 74 | -masm-syntax-unified \ 75 | -D_DEFAULT_SOURCE 76 | 77 | # C compiler flags 78 | CFLAGS := -std=c99 79 | 80 | # C++ compiler flags 81 | CXXFLAGS := -std=c++20 -fno-rtti -fno-exceptions 82 | 83 | # Assembler flags (as passed to GCC) 84 | ASFLAGS := 85 | 86 | # Linker flags (as passed to GCC) 87 | LDFLAGS := -mthumb \ 88 | $(if $(filter %_mb,$(PROJECT)),-specs=gba_mb.specs,-specs=gba.specs) 89 | 90 | # Uncomment this if you want to use Link Time Optimization 91 | # 92 | # USE_LTO := yes 93 | 94 | include build.mk 95 | -------------------------------------------------------------------------------- /src/link.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of 4-e . 3 | * 4 | * Copyright 2024 Mattie Behrens. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * “Software”), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject 12 | * to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 22 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #ifndef INCLUDE_LINK_H 27 | #define INCLUDE_LINK_H 28 | 29 | #include 30 | 31 | #define HANDSHAKE_1 0xfbfb 32 | #define HANDSHAKE_2 0x5841 33 | #define HANDSHAKE_3 0x4534 34 | 35 | #define GAME_REQUEST_DEMO 0xecec 36 | #define GAME_REQUEST_POWERUP 0xeded 37 | #define GAME_REQUEST_LEVEL 0xeeee 38 | 39 | #define GAME_ANIMATING 0xf3f3 40 | #define EREADER_ANIMATING 0xf2f2 41 | #define EREADER_READY 0xf1f1 42 | 43 | #define GAME_READY_DEMO 0xefef 44 | #define GAME_READY_POWERUP 0xf0f0 45 | #define GAME_READY_LEVEL 0xfafa 46 | 47 | #define EREADER_CANCEL 0xf7f7 48 | 49 | #define EREADER_SEND_READY 0xf9f9 50 | #define GAME_RECEIVE_READY 0xfefe 51 | 52 | #define EREADER_SEND_START 0xfdfd 53 | #define EREADER_SEND_END 0xfcfc 54 | 55 | #define GAME_RECEIVE_OK 0xf5f5 56 | #define GAME_RECEIVE_ERROR 0xf4f4 57 | 58 | #define EREADER_SIO_END 0xf3f3 59 | #define GAME_SIO_END 0xf1f1 60 | 61 | void activateLink(); 62 | void waitForPlayerAssignment(); 63 | void send(u16 data); 64 | void sendAndExpect(u16 data, u16 expect); 65 | u16 sendAndReceiveExcept(u16 data, u16 except); 66 | 67 | #endif /* INCLUDE_LINK_H */ 68 | -------------------------------------------------------------------------------- /src/link.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of 4-e . 3 | * 4 | * Copyright 2024 Mattie Behrens. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * “Software”), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject 12 | * to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 22 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | 28 | #include "link.h" 29 | #include "ui.h" 30 | 31 | void activateLink() 32 | { 33 | irq_enable(II_SERIAL); 34 | irq_enable(II_KEYPAD); 35 | REG_KEYCNT = KEY_B | KCNT_IRQ; 36 | 37 | REG_RCNT = REG_RCNT & ~R_MODE_GPIO; 38 | REG_SIOCNT = SIO_MODE_MULTI; 39 | REG_SIOCNT = REG_SIOCNT | SIO_IRQ | SIOM_CONNECTED | SIOM_SLAVE | SIOM_115200; 40 | } 41 | 42 | void waitForPlayerAssignment() 43 | { 44 | do 45 | { 46 | IntrWait(1, IRQ_SERIAL | IRQ_KEYPAD); 47 | if (~REG_KEYINPUT & KEY_B) 48 | done("Cancelled.", NULL); 49 | } while ((REG_SIOCNT & SIOM_ID_MASK) == 0); 50 | } 51 | 52 | void send(u16 data) 53 | { 54 | IntrWait(1, IRQ_SERIAL | IRQ_KEYPAD); 55 | if (~REG_KEYINPUT & KEY_B) 56 | done("Cancelled.", NULL); 57 | REG_SIOMLT_SEND = data; 58 | } 59 | 60 | void sendAndExpect(u16 data, u16 expect) 61 | { 62 | u16 received; 63 | do 64 | { 65 | send(data); 66 | received = REG_SIOMULTI0; 67 | } while (received != expect); 68 | } 69 | 70 | u16 sendAndReceiveExcept(u16 data, u16 except) 71 | { 72 | u16 received; 73 | do 74 | { 75 | send(data); 76 | received = REG_SIOMULTI0; 77 | } while (received == except); 78 | return received; 79 | } 80 | -------------------------------------------------------------------------------- /src/ui.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of 4-e . 3 | * 4 | * Copyright 2024 Mattie Behrens. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * “Software”), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject 12 | * to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 22 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | #include "link.h" 30 | 31 | void initScreen() 32 | { 33 | REG_DISPCNT = DCNT_MODE0 | DCNT_BG0; 34 | tte_init_se_default(0, BG_CBB(0) | BG_SBB(31)); 35 | tte_init_con(); 36 | } 37 | 38 | void clearScreen() 39 | { 40 | tte_erase_screen(); 41 | tte_set_pos(0, 152); 42 | // ----+----1----+----2----+----3 43 | tte_write("mattiebee.dev/4-e v2.1"); 44 | tte_set_pos(0, 0); 45 | } 46 | 47 | void status(const char *message, const char *name) 48 | { 49 | clearScreen(); 50 | 51 | if (name != NULL) tte_write(name); 52 | 53 | int len = strlen(message); 54 | int x = 120 - (len * 4); 55 | 56 | tte_set_pos(x, 76); 57 | tte_write(message); 58 | } 59 | 60 | void done(const char *message, const char *name) 61 | { 62 | clearScreen(); 63 | 64 | if (name != NULL) tte_write(name); 65 | 66 | int len = strlen(message); 67 | int x = 120 - (len * 4); 68 | 69 | tte_set_pos(x, 68); 70 | tte_write(message); 71 | 72 | tte_set_pos(16, 84); 73 | tte_write("Press any button to reset."); 74 | 75 | while (REG_KEYINPUT != KEY_MASK) 76 | VBlankIntrWait(); 77 | while (REG_KEYINPUT == KEY_MASK) 78 | VBlankIntrWait(); 79 | while (REG_KEYINPUT != KEY_MASK) 80 | VBlankIntrWait(); 81 | 82 | SoftReset(); 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 4-e 2 | 3 | A project to send [Super Mario Advance 4 | 4](https://www.mariowiki.com/Super_Mario_Advance_4:_Super_Mario_Bros._3) 5 | [e-Reader](https://www.mariowiki.com/E-Reader) cards from one Game 6 | Boy Advance running 4-e to another running the game, using homebrew 7 | and decoded .bin versions of those cards instead of printed cards 8 | an an actual e-Reader. 9 | 10 | It has been tested to successfully and reliably send demo, power-up, 11 | and level cards between mGBA multiplayer windows as well as real 12 | Game Boys Advance and [Analogue Pockets](https://www.analogue.co/pocket). 13 | 14 | I've written more about 4-e [on my 15 | blog](https://mattiebee.io/52283/introducing-4-e-super-mario-advance-4-e-cards-without-an-e-reader). 16 | 17 | ## Usage 18 | 19 | 1. Build the 4-e.gba ROM or download it from 20 | [releases](https://github.com/mattieb/4-e/releases). 21 | 22 | 2. Using [gbfs-web](https://mattiebee.app/gbfs-web), attach the 23 | e-Card .bin files you wish to use to 4-e.gba, and save a new 24 | ROM. 25 | 26 | - You can also use the standard tools from 27 | [GBFS](https://pineight.com/gba/#gbfs) to create a GBFS 28 | file, then concatenate it to 4-e.gba. (These tools are 29 | also included in the "gba-tools" package in devkitARM.) 30 | 31 | 3. Link two Game Boy Advance systems with a link cable. The game 32 | must be player 1, and 4-e must be player 2. 33 | 34 | 4. On the first system, run Super Mario Advance 4. 35 | 36 | 5. On the second system, run your custom 4-e ROM. 37 | 38 | 6. Pick the e-Card you wish you send from the list. If your GBFS 39 | file only had one e-Card in it, it will be selected automatically. 40 | From this point on, you can cancel and reset by pressing B. 41 | 42 | 7. On the first system, start the e-Reader communication process. 43 | 4-e will connect to the game automatically and try to send your 44 | card. 45 | 46 | 8. When the game has finished, press any button on 4-e to reset 47 | so you can send another card if you wish. 48 | 49 | ## Troubleshooting 50 | 51 | ### 4-e doesn't respond when I start communication 52 | 53 | Check that your link cable is connected firmly and correctly. 54 | 55 | - If you're using the official Nintendo link cable, it must be 56 | connected with the purple end to the game and the gray end to 57 | 4-e. 58 | 59 | - If you're using the Analogue Pocket link cable, it must be 60 | switched to GBA mode, not GB/C mode. 61 | 62 | ## Thanks 63 | 64 | 4-e wouldn't exist without: 65 | 66 | - [devkitARM](https://devkitpro.org/wiki/devkitARM) 67 | 68 | - [gba-makefile-template](https://github.com/gbadev-org/gba-makefile-template) 69 | 70 | - GBATEK (specifically the section on [GBA Communication 71 | Ports](https://problemkaputt.de/gbatek.htm#gbacommunicationports)) 72 | 73 | - [GBFS](https://pineight.com/gba/#gbfs) 74 | 75 | - [mGBA](https://mgba.io) 76 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of 4-e . 3 | * 4 | * Copyright 2024 Mattie Behrens. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * “Software”), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject 12 | * to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 22 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | #include "gbfs.h" 30 | #include "link.h" 31 | #include "pick.h" 32 | #include "ui.h" 33 | 34 | #define CARD_HEADER_OFFSET 0x72 35 | 36 | bool connect() 37 | { 38 | sendAndExpect(HANDSHAKE_1, HANDSHAKE_1); 39 | sendAndExpect(HANDSHAKE_2, HANDSHAKE_2); 40 | sendAndExpect(HANDSHAKE_3, HANDSHAKE_3); 41 | 42 | u16 cardRequest = sendAndReceiveExcept(HANDSHAKE_3, HANDSHAKE_3); 43 | 44 | sendAndExpect(GAME_ANIMATING, EREADER_ANIMATING); 45 | send(EREADER_ANIMATING); 46 | 47 | switch (cardRequest) 48 | { 49 | case GAME_REQUEST_DEMO: 50 | sendAndExpect(EREADER_READY, GAME_READY_DEMO); 51 | break; 52 | 53 | case GAME_REQUEST_POWERUP: 54 | sendAndExpect(EREADER_READY, GAME_READY_POWERUP); 55 | break; 56 | 57 | case GAME_REQUEST_LEVEL: 58 | sendAndExpect(EREADER_READY, GAME_READY_LEVEL); 59 | break; 60 | 61 | default: 62 | return true; 63 | } 64 | 65 | sendAndExpect(EREADER_SEND_READY, GAME_RECEIVE_READY); 66 | return false; 67 | } 68 | 69 | void sendBin(const void *file, u32 len) 70 | { 71 | send(EREADER_SEND_START); 72 | u32 checksum = 0; 73 | for (off_t o = CARD_HEADER_OFFSET; o < len; o += 2) 74 | { 75 | u16 block = *(u16 *)(file + o); 76 | send(block); 77 | checksum += block; 78 | } 79 | send(checksum & 0xffff); 80 | send(checksum >> 16); 81 | send(EREADER_SEND_END); 82 | } 83 | 84 | int main(void) 85 | { 86 | irq_init(NULL); 87 | irq_enable(II_VBLANK); 88 | 89 | initScreen(); 90 | 91 | const GBFS_FILE *gbfs = find_first_gbfs_file((void *)find_first_gbfs_file); 92 | if (!gbfs) 93 | { 94 | done("No attached GBFS file found.", NULL); 95 | } 96 | 97 | size_t n = gbfs->dir_nmemb == 1 ? 0 : pickBin(gbfs); 98 | 99 | char name[25]; 100 | u32 len; 101 | const void *file = gbfs_get_nth_obj(gbfs, n, name, &len); 102 | 103 | status("Waiting... (B=cancel)", name); 104 | activateLink(); 105 | waitForPlayerAssignment(); 106 | 107 | status("Connecting... (B=cancel)", name); 108 | if (connect()) 109 | { 110 | done("Connection failed.", name); 111 | } 112 | 113 | status("Sending... (B=cancel)", name); 114 | sendBin(file, len); 115 | 116 | done("Card data sent!", name); 117 | } 118 | -------------------------------------------------------------------------------- /src/pick.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of 4-e . 3 | * 4 | * Copyright 2024 Mattie Behrens. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining 7 | * a copy of this software and associated documentation files (the 8 | * “Software”), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to 11 | * permit persons to whom the Software is furnished to do so, subject 12 | * to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 21 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 22 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | #include "gbfs.h" 30 | #include "pick.h" 31 | #include "ui.h" 32 | 33 | void waitUp(u16 key) 34 | { 35 | while (~REG_KEYINPUT & key) 36 | VBlankIntrWait(); 37 | } 38 | 39 | size_t pickBin(const GBFS_FILE *gbfs) 40 | { 41 | u16 count = gbfs->dir_nmemb; 42 | u16 page = 0; 43 | u16 selection = 0; 44 | 45 | while (true) 46 | { 47 | clearScreen(); 48 | 49 | tte_printf("+=move, A=pick %5d/%5d\n\n", selection + 1, count); 50 | 51 | for (int n = page; n < count && n < page + PICKER_PAGE_SIZE; n++) 52 | { 53 | char name[25] = {0}; 54 | gbfs_get_nth_obj(gbfs, n, name, NULL); 55 | if (n == selection) 56 | { 57 | tte_write(" > "); 58 | } 59 | else 60 | { 61 | tte_write(" "); 62 | } 63 | tte_write(name); 64 | tte_write("\n"); 65 | } 66 | 67 | while (true) 68 | { 69 | VBlankIntrWait(); 70 | 71 | if (~REG_KEYINPUT & KEY_DOWN) 72 | { 73 | if (selection < (count - 1)) selection++; 74 | waitUp(KEY_DOWN); 75 | break; 76 | } 77 | 78 | if (~REG_KEYINPUT & KEY_UP) 79 | { 80 | if (!selection == 0) selection--; 81 | waitUp(KEY_UP); 82 | break; 83 | } 84 | 85 | if (~REG_KEYINPUT & KEY_RIGHT) 86 | { 87 | if (selection <= (count - 1 - PICKER_PAGE_SIZE)) 88 | selection += PICKER_PAGE_SIZE; 89 | else 90 | selection = (count - 1); 91 | waitUp(KEY_RIGHT); 92 | break; 93 | } 94 | 95 | if (~REG_KEYINPUT & KEY_LEFT) 96 | { 97 | if (selection >= PICKER_PAGE_SIZE) 98 | selection -= PICKER_PAGE_SIZE; 99 | else 100 | selection = 0; 101 | waitUp(KEY_LEFT); 102 | break; 103 | } 104 | 105 | if (~REG_KEYINPUT & KEY_A) 106 | { 107 | return selection; 108 | } 109 | } 110 | 111 | page = (selection / PICKER_PAGE_SIZE) * PICKER_PAGE_SIZE; 112 | } 113 | 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # Notes 2 | 3 | ## Connection 4 | 5 | The game and e-Reader (specifically, the game's custom e-Reader 6 | dotcode scanner, which is separately sent by the game to the e-Reader 7 | and saved there for future use) communicate over [SIO multiplayer 8 | mode](https://problemkaputt.de/gbatek.htm#siomultiplayermode). 9 | 10 | The game is player 0 and master; the e-Reader is player 1. 11 | 12 | Baud rate is 115200. 13 | 14 | As master, the game drives the serial clock. The e-Reader waits for 15 | the game to be ready before sending any data. Having our program 16 | wait for serial interrupts before sending data appears to work very 17 | reliably. 18 | 19 | ## Card scanning protocol 20 | 21 | When the e-Reader detects that the connection has been established, 22 | it starts the protocol: 23 | 24 | 1. The e-Reader sends FBFB until the game replies FBFB. 25 | 26 | 2. The e-Reader sends 5841 until the game replies 5841. 27 | 28 | 3. The e-Reader sends 4534 until the game replies 4534. 29 | 30 | 4. Next, the game takes control, sending one of the following: 31 | 32 | - ECEC to request a demo card. 33 | 34 | - EDED to request a power-up card. 35 | 36 | - EEEE to request a level card. 37 | 38 | The e-Reader replies F3F3. Both the game and the e-Reader 39 | continue to send F3F3 and the request message while Lakitu flies 40 | off the game screen. 41 | 42 | 5. The game sends F2F2 and the e-Reader replies F2F2. Both the 43 | game and the e-Reader continue to send F2F2 while Lakitu flies onto 44 | the e-Reader screen. 45 | 46 | 6. The e-Reader sends F1F1. 47 | 48 | 7. The game begins sending one of the following, expecting a scan to begin: 49 | 50 | - EFEF when ready to receive a demo card. 51 | 52 | - F0F0 when ready to receive a power-up card. 53 | 54 | - FAFA when ready to receive a level card. 55 | 56 | 8. The e-Reader can cancel at this point by sending F7F7, if B is 57 | pressed. It will then start [shutdown](#shutdown-protocol). 58 | 59 | 9. Once the e-Reader is ready to send, it sends F9F9. The game 60 | begins sending FEFE repeatedly. 61 | 62 | 10. The e-Reader sends FDFD. The game stops sending, but is still 63 | responsible for the serial clock. 64 | 65 | 11. The e-Reader sends the contents of the card, minus the 114-byte 66 | header, in two-byte packets. They appear byteswapped in mGBA 67 | logs (e.g. bytes "CE CF" in the decoded card appear as "CFCE") 68 | but, in code, should be loaded as they appear (e.g. "0xcecf"). 69 | 70 | 12. While transmitting, the e-Reader calculates a checksum by adding 71 | each 16-bit packet value (e.g. "0xcecf" as in the last example) 72 | to a 32-bit value that was initialized as zero. At the conclusion 73 | of the card, this checksum is transmitted into two 16-bit 74 | packets; least-significant first, most-significant second. 75 | 76 | 13. The e-Reader sends FCFC to end the card transmission. 77 | 78 | 14. If there was an transmission error (e.g. checksum failure), the 79 | game sends F4F4. _(Next steps are currently undocumented.)_ 80 | 81 | 15. The game sends F5F5 to acknowledge a successful transmission, 82 | and the e-Reader begins [shutdown](#shutdown-protocol). 83 | 84 | ## Shutdown protocol 85 | 86 | The e-Reader will do a clean shutdown of the communication session 87 | in certain circumstances. 88 | 89 | 1. The e-Reader sends F2F2 while Lakitu flies off the screen. 90 | 91 | 2. The e-Reader sends F3F3 as its last packet, then stops transmitting. 92 | 93 | 3. The game then sends F1F1 as its last packet, then stops 94 | transmitting. 95 | 96 | At the conclusion of this protocol, the e-Reader is ready to restart 97 | communications [from the beginning](#card-scanning-protocol) as if 98 | it has just been started. 99 | 100 | ## Formats 101 | 102 | ### raw 103 | 104 | .raw files are the complete binary data of the e-Reader card, with 105 | error-correction applied, which can be rendered as dotcodes. 106 | 107 | [nedcenc](https://github.com/Lymia/nedclib) can translate between 108 | .raw and [.bin](#bin) files. 109 | 110 | ### bin 111 | 112 | .bin files are the complete binary data of the e-Reader card, without 113 | error-correction applied. The e-Reader will decode cards to this 114 | internally. 115 | 116 | | Offset | Size | Value | 117 | | ------ | ---- | ----- | 118 | | 0 | 114 | Card header | 119 | | 114 | 1998 | [Card contents](#card-contents) | 120 | 121 | The e-Reader requires the card header to be intact and correct. 4-e 122 | will ignore the card header entirely, seeking 114 bytes into the 123 | .bin file. 124 | 125 | ### sav 126 | 127 | A Super Mario Advance 4 Flash save can "save" up to 32 levels, and 128 | an additional in-progress level that, when beaten, moves to one of 129 | the saved level slots. 130 | 131 | | Offset | Size | Value | 132 | | ------ | ---- | ----- | 133 | | 800 | 1998 | In-progress [level contents](#level-contents) | 134 | | 24592 | 1998 | Saved level 1 contents | 135 | | 26624 | 1998 | Saved level 2 contents | 136 | | 28688 | 1998 | Saved level 3 contents | 137 | | 30720 | 1998 | Saved level 4 contents | 138 | | 32784 | 1998 | Saved level 5 contents | 139 | | 34816 | 1998 | Saved level 6 contents | 140 | | 36880 | 1998 | Saved level 7 contents | 141 | | 38912 | 1998 | Saved level 8 contents | 142 | | 40976 | 1998 | Saved level 9 contents | 143 | | 43008 | 1998 | Saved level 10 contents | 144 | | 45072 | 1998 | Saved level 11 contents | 145 | | 47104 | 1998 | Saved level 12 contents | 146 | | 49168 | 1998 | Saved level 13 contents | 147 | | 51200 | 1998 | Saved level 14 contents | 148 | | 53264 | 1998 | Saved level 15 contents | 149 | | 55296 | 1998 | Saved level 16 contents | 150 | | 57360 | 1998 | Saved level 17 contents | 151 | | 59392 | 1998 | Saved level 18 contents | 152 | | 61456 | 1998 | Saved level 19 contents | 153 | | 63488 | 1998 | Saved level 20 contents | 154 | | 67584 | 1998 | In-progress level contents backup copy | 155 | | 90128 | 1998 | Saved level 21 contents | 156 | | 92160 | 1998 | Saved level 22 contents | 157 | | 94224 | 1998 | Saved level 23 contents | 158 | | 96256 | 1998 | Saved level 24 contents | 159 | | 98320 | 1998 | Saved level 25 contents | 160 | | 100352 | 1998 | Saved level 26 contents | 161 | | 102416 | 1998 | Saved level 27 contents | 162 | | 104448 | 1998 | Saved level 28 contents | 163 | | 106512 | 1998 | Saved level 29 contents | 164 | | 108544 | 1998 | Saved level 30 contents | 165 | | 110608 | 1998 | Saved level 31 contents | 166 | | 112640 | 1998 | Saved level 32 contents | 167 | 168 | The saved level offsets seem a bit arbitrary but, in fact, follow 169 | a pattern: 170 | 171 | - For slots 1 to 19, start at offset 24576, and for each slot, 172 | move 2048 bytes forward. (24576, 26624, 28672, 30702, etc.) 173 | 174 | - For slots 20 to 32, start at offset 90112, and for each slot, 175 | move 2048 bytes forward. (90112, 92160, 94208, 96256, etc.) 176 | 177 | - For odd-numbered slots (1, 3, etc.), the contents start an 178 | additional 16 bytes in. (24592, 28688, etc.) 179 | 180 | ### Card contents 181 | 182 | Card contents are transmitted in their entirety during the contents 183 | step of the [card scanning protocol](#card-scanning-protocol). 184 | 185 | They are always 1998 bytes long. 186 | 187 | #### Level contents 188 | 189 | When the [card contents](#card-contents) is a level: 190 | 191 | | Offset | Size | Value | 192 | | ------ | ---- | ----- | 193 | | 0 | 1 | Level id | 194 | | 1 | 7 | Bytes: 00 ff ff ff ff ff 00 195 | | 8 | 1990 | [lcmp](#lcmp) | 196 | 197 | The level id matches the last part of the [e-Card 198 | number](https://www.mariowiki.com/Super_Mario_Advance_4:_Super_Mario_Bros._3_e-Reader_cards); 199 | for example, Classic World 1-1 is numbered 07-A001 and has level 200 | id is 1, and Airship's Revenge is numbered 07-A051 and has level 201 | id is 51. 202 | 203 | Official level ids 1-5 are "Classic" levels, 11-40 standard levels, 204 | and 50-53 promotional levels. Re-using official level ids may cause 205 | these levels to be overwritten. 206 | 207 | While it's apparently technically possible to use any level id, at 208 | least one 209 | [report](https://discord.com/channels/884534133496365157/884534133496365160/1223234994907119667) 210 | on the Smaghetti Discord suggests level ids higher than 72 (decimal) 211 | will "mess up the level list". 212 | 213 | ### lcmp 214 | 215 | .lcmp files are the portion of a level card that contains the 216 | compressed level data itself, compressed using what is known as 217 | "ASR0" compression. 218 | 219 | | Offset | Size | Value | 220 | | ------ | ---- | ----- | 221 | | 0 | 4 | `"ASR0"` | 222 | | 4 | 1986 | Compressed level data | 223 | 224 | .lcmp files can be converted to [level contents](#level-contents), 225 | given a level id. 226 | 227 | .lcmp files can be decompressed into [.level](#level) files and 228 | compressed again with sma4comp. 229 | 230 | ### level 231 | 232 | .level files are the uncompressed level data. 233 | --------------------------------------------------------------------------------