From e10058c7d91d35f3b674020c3f5540212f4e38cd Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 24 Mar 2025 13:49:37 +0100 Subject: [PATCH 1/6] Proof of concept: add a loader that embeds the multiboot rom and allows to load it from a flashcart --- Makefile | 3 + docker-compose.yml | 17 +++ loader/Makefile | 188 +++++++++++++++++++++++++++++++ loader/graphics/ptgb_logo_l.grit | 1 + loader/graphics/ptgb_logo_l.png | 1 + loader/graphics/ptgb_logo_r.grit | 1 + loader/graphics/ptgb_logo_r.png | 1 + loader/source/main.c | 105 +++++++++++++++++ 8 files changed, 317 insertions(+) create mode 100644 docker-compose.yml create mode 100644 loader/Makefile create mode 120000 loader/graphics/ptgb_logo_l.grit create mode 120000 loader/graphics/ptgb_logo_l.png create mode 120000 loader/graphics/ptgb_logo_r.grit create mode 120000 loader/graphics/ptgb_logo_r.png create mode 100644 loader/source/main.c diff --git a/Makefile b/Makefile index 202bbe1..d3e808e 100644 --- a/Makefile +++ b/Makefile @@ -139,10 +139,13 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(BUILD): @[ -d $@ ] || mkdir -p $@ @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + cp $(TARGET).gba loader/data/multiboot_rom.bin + @$(MAKE) -C loader #--------------------------------------------------------------------------------- clean: @echo clean ... + @$(MAKE) -C loader clean @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d26b5fc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,17 @@ +# docker-compose run --service-ports build + +version: "3.5" + +services: + build: + image: devkitpro/devkitarm + working_dir: /usr/Poke_Transporter_GB + command: tail -F anything + volumes: + - type: bind + source: ${SSH_AUTH_SOCK} + target: ${SSH_AUTH_SOCK} + - type: bind + source: ${PWD} + target: /usr/Poke_Transporter_GB + diff --git a/loader/Makefile b/loader/Makefile new file mode 100644 index 0000000..f87885d --- /dev/null +++ b/loader/Makefile @@ -0,0 +1,188 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/gba_rules + +#--------------------------------------------------------------------------------- +# the LIBGBA path is defined in gba_rules, but we have to define LIBTONC ourselves +#--------------------------------------------------------------------------------- +LIBTONC := $(DEVKITPRO)/libtonc + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +# DATA is a list of directories containing binary data +# GRAPHICS is a list of directories containing files to be processed by grit +# +# All directories are specified relative to the project directory where +# the makefile is found +# +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +INCLUDES := include +DATA := data +MUSIC := +GRAPHICS := graphics + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -mthumb -mthumb-interwork + +CFLAGS := -g -Wall -O2\ + -mcpu=arm7tdmi -mtune=arm7tdmi\ + $(ARCH) + +CFLAGS += $(INCLUDE) + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -lmm -ltonc + + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib. +# the LIBGBA path should remain in this list if you want to use maxmod +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBGBA) $(LIBTONC) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- + + +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +ifneq ($(strip $(MUSIC)),) + export AUDIOFILES := $(foreach dir,$(notdir $(wildcard $(MUSIC)/*.*)),$(CURDIR)/$(MUSIC)/$(dir)) + BINFILES += soundbank.bin +endif + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) + +export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export OFILES_GRAPHICS := $(PNGFILES:.png=.o) + +export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) $(OFILES_GRAPHICS) + +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) $(PNGFILES:.png=.h) + +export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -f data/* + @rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba + + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- + +$(OUTPUT).gba : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +$(OFILES_SOURCES) : $(HFILES) + +#--------------------------------------------------------------------------------- +# The bin2o rule should be copied and modified +# for each extension used in the data directories +#--------------------------------------------------------------------------------- + +#--------------------------------------------------------------------------------- +# rule to build soundbank from music files +#--------------------------------------------------------------------------------- +soundbank.bin soundbank.h : $(AUDIOFILES) +#--------------------------------------------------------------------------------- + @mmutil $^ -osoundbank.bin -hsoundbank.h + +#--------------------------------------------------------------------------------- +# This rule links in binary data with the .bin extension +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------- +# This rule creates assembly source files using grit +# grit takes an image file and a .grit describing how the file is to be processed +# add additional rules like this for each image extension +# you use in the graphics folders +#--------------------------------------------------------------------------------- +%.s %.h: %.png %.grit +#--------------------------------------------------------------------------------- + @echo "grit $(notdir $<)" + @grit $< -fts -o$* + +# make likes to delete intermediate files. This prevents it from deleting the +# files generated by grit after building the GBA ROM. +.SECONDARY: + +-include $(DEPSDIR)/*.d +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/loader/graphics/ptgb_logo_l.grit b/loader/graphics/ptgb_logo_l.grit new file mode 120000 index 0000000..6242da8 --- /dev/null +++ b/loader/graphics/ptgb_logo_l.grit @@ -0,0 +1 @@ +../../graphics/ptgb_logo_l.grit \ No newline at end of file diff --git a/loader/graphics/ptgb_logo_l.png b/loader/graphics/ptgb_logo_l.png new file mode 120000 index 0000000..467b81a --- /dev/null +++ b/loader/graphics/ptgb_logo_l.png @@ -0,0 +1 @@ +../../graphics/ptgb_logo_l.png \ No newline at end of file diff --git a/loader/graphics/ptgb_logo_r.grit b/loader/graphics/ptgb_logo_r.grit new file mode 120000 index 0000000..8e35774 --- /dev/null +++ b/loader/graphics/ptgb_logo_r.grit @@ -0,0 +1 @@ +../../graphics/ptgb_logo_r.grit \ No newline at end of file diff --git a/loader/graphics/ptgb_logo_r.png b/loader/graphics/ptgb_logo_r.png new file mode 120000 index 0000000..193ab3b --- /dev/null +++ b/loader/graphics/ptgb_logo_r.png @@ -0,0 +1 @@ +../../graphics/ptgb_logo_r.png \ No newline at end of file diff --git a/loader/source/main.c b/loader/source/main.c new file mode 100644 index 0000000..6fe83f3 --- /dev/null +++ b/loader/source/main.c @@ -0,0 +1,105 @@ +#include + +// This file is autogenerated from the file in the graphics folder +#include "ptgb_logo_l.h" +#include "ptgb_logo_r.h" +#include "multiboot_rom_bin.h" + +#define SPRITE_CHAR_BLOCK 4 + +enum SpriteTypes +{ + SPRITE_LOGO_L, + SPRITE_LOGO_R +}; + +enum PaletteTypes +{ + PAL_LOGO +}; + +OBJ_ATTR obj_buffer[128]; +OBJ_ATTR* logoL = &obj_buffer[SPRITE_LOGO_L]; +OBJ_ATTR* logoR = &obj_buffer[SPRITE_LOGO_R]; + +/** + * Taken from sprite_data.cpp + */ +static void loadSprite(OBJ_ATTR *sprite, const unsigned int objTiles[], int objTilesLen, + u32* tile_id, u32 pal_bank, int attr0, int attr1, u32 priority) +{ + memcpy32(&tile_mem[SPRITE_CHAR_BLOCK][*tile_id], objTiles, objTilesLen); + obj_set_attr(sprite, attr0, attr1, ATTR2_PALBANK(pal_bank) | *tile_id | ATTR2_PRIO(priority)); + *tile_id += objTilesLen / 32; + obj_hide(sprite); +}; + +static void load_logo(u32* curSpriteIndex) +{ + // load palette + memcpy32(pal_obj_mem + (PAL_LOGO * 16), ptgb_logo_lPal, ptgb_logo_lPalLen); + + // now load the tiles of the logo + loadSprite(logoL, ptgb_logo_lTiles, ptgb_logo_lTilesLen, curSpriteIndex, PAL_LOGO, ATTR0_SQUARE, ATTR1_SIZE_64x64, 1); + loadSprite(logoR, ptgb_logo_rTiles, ptgb_logo_rTilesLen, curSpriteIndex, PAL_LOGO, ATTR0_SQUARE, ATTR1_SIZE_64x64, 1); + + // Set position + obj_set_pos(logoL, 56, 12); + obj_set_pos(logoR, 56 + 64, 12); + obj_unhide_multi(logoL, 1, 2); + + oam_copy(oam_mem, obj_buffer, 2); // Update first OAM object +} + +/** + * Loads the PokeTransporter multiboot rom into EWRAM + */ +static void load_multiboot_rom(const void *src, void *dst, u32 size) +{ + u8 *s = (u8*)src; + u8 *d = (u8*)dst; + + for (u32 i = 0; i < size; i++) + { + d[i] = s[i]; + } +} + +/** + * Now jump to the multiboot rom address and start execution + */ +void execute_multiboot() +{ + // Disable interrupts + REG_IME = 0; + + // Set up waitstates (multiboot defaults to faster EWRAM access) + REG_WAITCNT = 0x4317; + + // Function pointer to EWRAM execution entry + void (*entry)(void) = (void*)0x020000C0; + entry(); // Jump to loaded ROM +} + +int main(void) +{ + u32 curSpriteIndex = 0; + + irq_init(NULL); + irq_enable(II_VBLANK); + + tte_init_chr4c_default(0, BG_CBB(0) | BG_SBB(31)); + tte_set_pos(92, 68); + tte_write("Loading..."); + + oam_init(obj_buffer, 128); + + load_logo(&curSpriteIndex); + + REG_DISPCNT = DCNT_MODE0 | DCNT_BG0 | DCNT_OBJ | DCNT_OBJ_1D; + + VBlankIntrWait(); + + load_multiboot_rom(multiboot_rom_bin, (void*)0x02000000, multiboot_rom_bin_size); + execute_multiboot(); +} From 3d02b1aea79b828fd44226cf6a4672c2e941ea71 Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 24 Mar 2025 14:06:38 +0100 Subject: [PATCH 2/6] Add some comments and a README --- docker-compose.yml | 12 +++++++++++- loader/README.md | 7 +++++++ loader/source/main.c | 25 +++++++++++++++---------- 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 loader/README.md diff --git a/docker-compose.yml b/docker-compose.yml index d26b5fc..e55dc72 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,14 @@ -# docker-compose run --service-ports build +# This docker-compose file allows you to pull the devkitpro/devkitarm docker container +# and use it to build the Poke Transporter GB rom(s). +# +# To launch the container itself in the background: +# docker compose up -d +# Note the container name being printed in the terminal. In my case it was this: poke_transporter_gb-build-1 +# +# Then get a terminal into the container like this: +# docker exec -it poke_transporter_gb-build-1 /bin/bash +# +# Now you can build the project inside this terminal. version: "3.5" diff --git a/loader/README.md b/loader/README.md new file mode 100644 index 0000000..173e1d0 --- /dev/null +++ b/loader/README.md @@ -0,0 +1,7 @@ +This folder contains a Proof of Concept to load PokeTransporter GB WITHOUT doing the whole multiboot ritual. + +It works by embedding the multiboot rom that is generated from the main project build into a separate "normal" gba rom. +And this loader rom will just copy the multiboot rom to EWRAM and jump to the right entrypoint to launch it. + +This allows the user to simply launch the loader from a simple flashcart, such as a supercard SD. +After PokeTransporter GB is loaded, the user should be able to swap out the flashcart and insert the Pokémon cartridge. \ No newline at end of file diff --git a/loader/source/main.c b/loader/source/main.c index 6fe83f3..c1d621a 100644 --- a/loader/source/main.c +++ b/loader/source/main.c @@ -6,13 +6,21 @@ #include "multiboot_rom_bin.h" #define SPRITE_CHAR_BLOCK 4 +#define MULTIBOOT_ENTRY_POINT (void*)0x020000C0 +/** + * An enum with an entry for every separate sprite we load in. + * We use this to ensure a valid index for each sprite + */ enum SpriteTypes { SPRITE_LOGO_L, SPRITE_LOGO_R }; +/** + * Similarly, the PaletteTypes enum has an entry for every separate palette we're using here. + */ enum PaletteTypes { PAL_LOGO @@ -34,6 +42,9 @@ static void loadSprite(OBJ_ATTR *sprite, const unsigned int objTiles[], int objT obj_hide(sprite); }; +/** + * This function exists to simply load and position the Poke Transporter GB logo + */ static void load_logo(u32* curSpriteIndex) { // load palette @@ -54,15 +65,9 @@ static void load_logo(u32* curSpriteIndex) /** * Loads the PokeTransporter multiboot rom into EWRAM */ -static void load_multiboot_rom(const void *src, void *dst, u32 size) +static void load_multiboot_rom(const void *src, u32 size) { - u8 *s = (u8*)src; - u8 *d = (u8*)dst; - - for (u32 i = 0; i < size; i++) - { - d[i] = s[i]; - } + memcpy32(MULTIBOOT_ENTRY_POINT, src, size); } /** @@ -77,7 +82,7 @@ void execute_multiboot() REG_WAITCNT = 0x4317; // Function pointer to EWRAM execution entry - void (*entry)(void) = (void*)0x020000C0; + void (*entry)(void) = MULTIBOOT_ENTRY_POINT; entry(); // Jump to loaded ROM } @@ -100,6 +105,6 @@ int main(void) VBlankIntrWait(); - load_multiboot_rom(multiboot_rom_bin, (void*)0x02000000, multiboot_rom_bin_size); + load_multiboot_rom(multiboot_rom_bin, multiboot_rom_bin_size); execute_multiboot(); } From 65b0e10b48678730d2329a8debf0a9ffe62536d6 Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 24 Mar 2025 14:17:01 +0100 Subject: [PATCH 3/6] Fix bug with previous changes to execute_multiboot() --- loader/source/main.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/loader/source/main.c b/loader/source/main.c index c1d621a..299b0c9 100644 --- a/loader/source/main.c +++ b/loader/source/main.c @@ -1,4 +1,5 @@ #include +#include // This file is autogenerated from the file in the graphics folder #include "ptgb_logo_l.h" @@ -6,6 +7,7 @@ #include "multiboot_rom_bin.h" #define SPRITE_CHAR_BLOCK 4 +#define DST_EWRAM (void*)0x02000000 #define MULTIBOOT_ENTRY_POINT (void*)0x020000C0 /** @@ -67,7 +69,7 @@ static void load_logo(u32* curSpriteIndex) */ static void load_multiboot_rom(const void *src, u32 size) { - memcpy32(MULTIBOOT_ENTRY_POINT, src, size); + memcpy(DST_EWRAM, src, size); } /** @@ -76,6 +78,7 @@ static void load_multiboot_rom(const void *src, u32 size) void execute_multiboot() { // Disable interrupts + // During the time the user could swap out the cartridge, we should disable interrupts REG_IME = 0; // Set up waitstates (multiboot defaults to faster EWRAM access) From 75a19539aebb5315252b151ec4240f1ebaa288ab Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 24 Mar 2025 14:24:55 +0100 Subject: [PATCH 4/6] Update the main README in this branch to make the added support more explicit. I mean, this branch is still only a proof of concept. Not intended for merging directly. But adding it to the main README also makes this addition show up in the branches' github page. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 974dbe6..746b8e2 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,10 @@ Currently the only release is in English. Releases supporting other languages is Currently English versions of Red, Blue, Yellow, Gold, Silver, Crystal are supported- plus English, Spanish, German, Italian, French versions of Ruby, Sapphire, FireRed, LeafGreen, and Emerald. Compatibility for other languages is currently in development and will be added in the near future. ## Usage -Poké Transporter GB is a Multiboot program for the Game Boy Advance. There are two main ways to run the Poké Transporter GB ROM on a Game Boy Advance: +Poké Transporter GB is a Multiboot program for the Game Boy Advance. There are 3 main ways to run the Poké Transporter GB ROM on a Game Boy Advance: - Use a program such as FIX94's [GBA Link Cable ROM Sender](https://github.com/FIX94/gba-link-cable-rom-sender) on a GameCube or Wii running Homebrew along with a GBA GameCube Link Cable - Upload the Multiboot ROM to a GBA Flash Cart, launch the game in Multiboot mode (often by holding L when selecting the ROM), and swap the Game Pak after the program loads. +- Copy the loader.gba file to a GBA Flash Cart and swap the Game Pak after the program loads. This is useful for flashcarts that don't support launching multiboot roms directly. (SuperCard SD for example) Please note that transfering Pokémon will only work with a Game Boy Color Link Cable. Game Boy Advance Link Cables will not work. From 7c1fed707ef8d6c52b0ce23f4d69ca7fe572e6ee Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Mon, 24 Mar 2025 15:27:19 +0100 Subject: [PATCH 5/6] Don't forget to mkdir loader/data --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d3e808e..26a37b5 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,8 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) $(BUILD): @[ -d $@ ] || mkdir -p $@ @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile - cp $(TARGET).gba loader/data/multiboot_rom.bin + @mkdir -p loader/data + @cp $(TARGET).gba loader/data/multiboot_rom.bin @$(MAKE) -C loader #--------------------------------------------------------------------------------- From 9663c13f240ac801625edda273f27437698fab07 Mon Sep 17 00:00:00 2001 From: Philippe Symons Date: Tue, 25 Mar 2025 21:04:10 +0100 Subject: [PATCH 6/6] Make it work on my Supercard SD --- loader/source/main.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/loader/source/main.c b/loader/source/main.c index 299b0c9..cd544ca 100644 --- a/loader/source/main.c +++ b/loader/source/main.c @@ -67,23 +67,17 @@ static void load_logo(u32* curSpriteIndex) /** * Loads the PokeTransporter multiboot rom into EWRAM */ -static void load_multiboot_rom(const void *src, u32 size) +static void load_multiboot_rom(const void *src, size_t size) { + REG_IME = 0; // Disable all interrupts memcpy(DST_EWRAM, src, size); + REG_IME = 1; } - /** * Now jump to the multiboot rom address and start execution */ -void execute_multiboot() +static void execute_multiboot() { - // Disable interrupts - // During the time the user could swap out the cartridge, we should disable interrupts - REG_IME = 0; - - // Set up waitstates (multiboot defaults to faster EWRAM access) - REG_WAITCNT = 0x4317; - // Function pointer to EWRAM execution entry void (*entry)(void) = MULTIBOOT_ENTRY_POINT; entry(); // Jump to loaded ROM @@ -109,5 +103,6 @@ int main(void) VBlankIntrWait(); load_multiboot_rom(multiboot_rom_bin, multiboot_rom_bin_size); + execute_multiboot(); }