From 63d2513446faaa2217fc46efac8b0b4e1b74690f Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Wed, 6 May 2026 00:04:32 +0100 Subject: [PATCH 1/5] sa1 ports in ci, update readme --- .github/workflows/build.yml | 5 +- Makefile | 3 + README.md | 88 ++++++++++++++----- include/game/sa1/stage/stage_ui.h | 3 +- include/game/shared/stage/collision.h | 2 +- .../sa1/stage/enemies/boss_5_mecha_knuckles.c | 2 +- .../sa1/stage/interactables/forced_slide.c | 8 ++ src/game/sa1/stage/stage_ui.c | 8 +- src/game/sa1/stage/stage_ui_z.c | 2 +- src/game/shared/stage/demo_manager.c | 4 +- 10 files changed, 93 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ef90cf4e92..71d685f4f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -258,11 +258,12 @@ jobs: strategy: fail-fast: false matrix: + game: ["sa1", "sa2"] platform: ["sdl", "sdl_win32", "sdl_psp", "win32", "ps2"] os: ["ubuntu-24.04", "macos-15"] if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork || github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.fork}} runs-on: ${{ matrix.os }} - name: Build ${{ matrix.platform }} (${{ matrix.os }}) + name: Build ${{ matrix.game }} ${{ matrix.platform }} (${{ matrix.os }}) steps: - name: Checkout PR if: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_target'}} @@ -315,7 +316,7 @@ jobs: - name: ${{ matrix.platform }} run: | - make -j${nproc} ${{ matrix.platform }} + make -j${nproc} ${{ matrix.platform }} GAME_NAME=${{ matrix.game }} test: strategy: diff --git a/Makefile b/Makefile index c2468b4055..3dd14a121d 100644 --- a/Makefile +++ b/Makefile @@ -678,6 +678,9 @@ bribasa: sa1: @$(MAKE) GAME_NAME=sa1 +sa2: + @$(MAKE) GAME_NAME=sa2 + $(TOOLDIRS): tool_libs @$(MAKE) -C $@ diff --git a/README.md b/README.md index 66952306e7..26b16b0e30 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Sonic Advance 2

+

Sonic Advance Trilogy

Sonic Advance 2 title screen

@@ -12,9 +12,10 @@ > :warning: **This project is not completed and still under active development** -This is a work in progress matching decompilation of Sonic Advance 2 +This is a work in progress matching decompilation of Sonic Advance 1, 2, [& eventually 3](https://github.com/SAT-R/sa3/) It builds the following ROMs: +* [**sa1.gba**](https://datomatic.no-intro.org/index.php?page=show_record&s=23&n=0330) `sha1: eb00f101af23d728075ac2117e27ecd8a4b4c3e9` (Europe) (En,Ja,Fr,De,Es) * [**sa2.gba**](https://datomatic.no-intro.org/index.php?page=show_record&s=23&n=0890) `sha1: 7bcd6a07af7c894746fa28073fe0c0e34408022d` (USA) (En,Ja,Fr,De,Es,It) * [**sa2_europe.gba**](https://datomatic.no-intro.org/index.php?page=show_record&s=23&n=0900) `sha1: b0f64bdca097f2de8f05ac4c8caea2b80c5faeb1` (Europe) (En,Ja,Fr,De,Es,It) * [**sa2_japan.gba**](https://datomatic.no-intro.org/index.php?page=show_record&s=23&n=0799) `sha1: dffd0188fc78154b42b401398a224ae0713edf23` (Japan) (En,Ja,Fr,De,Es,It) @@ -28,7 +29,7 @@ It can also build: * **sa2.ps2.iso** `make ps2` (PlayStation 2 port, requires [PS2DEV](https://github.com/ps2dev/ps2dev)) * **EBOOT.PBP** `make sdl_psp` (PlayStation Portable SDL port, requires [PSPDEV](https://github.com/pspdev/pspdev)) -## Current state +## Current state (SA2) - :tada: The build is 100% from C files with [~60 functions which currently don't match](./asm/non_matching) - All assembly code extracted, disassembled, and decompiled by hand to their C equivilent @@ -40,38 +41,81 @@ It can also build: ## Sub games -Within the Sonic Advance 2 rom there are some "Sub games" which we have begun to decompile as part of this project. These sub games are automatically compiled and bundled during the main build process, as they are necessary to create a matching binary. +Within the Sonic Advance Trilogy roms there are some "Sub games" which we have begun to decompile as part of this project. These sub games are automatically compiled and bundled during the main build process, as they are necessary to create a matching binary. -| Multiboot rom | Decompilation | -| --- | --- | -| [**mb_chao_garden.gba**](./chao_garden) | ![Decompilation Progress](https://img.shields.io/endpoint?url=https://sat-r.github.io/sa2/reports/progress-mb_chao_garden-shield.json) | -| [**collect_rings.gba**](./multi_boot/roms/collect_rings/) | ![Decompilation Progress](https://img.shields.io/endpoint?url=https://sat-r.github.io/sa2/reports/progress-collect_rings-shield.json) | +| Game | Multiboot rom | Decompilation | +| --- | --- | --- | +| SA2 | [**mb_chao_garden.gba**](./chao_garden) | ![Decompilation Progress](https://img.shields.io/endpoint?url=https://sat-r.github.io/sa2/reports/progress-mb_chao_garden-shield.json) | +| SA2 | [**collect_rings.gba**](./multi_boot/roms/collect_rings/) | ![Decompilation Progress](https://img.shields.io/endpoint?url=https://sat-r.github.io/sa2/reports/progress-collect_rings-shield.json) | ## Setting up the repo Please see follow [these instructions](./INSTALL.md) -## Community - -Join us on [discord](https://discord.gg/vZTvVH3gA9) to get started in helping out - -## Screenshots - - -## Notes - -- The [Kirby & The Amazing Mirror](https://github.com/jiangzhengwenjz/katam/) decompilation uses a very similar codebase, as it was written by the same dev team (Dimps) -- https://decomp.me is a great resource for helping to create matching functions -- `ldscript.txt` tells the linker the order which files should be linked +## Contributing + +Join us on [discord](https://discord.gg/vZTvVH3gA9) to get started in helping out. Otherwise, we are always looking for people to document the code base, and there are [many non matching functions](./asm/non_matching) which need human input to resolve. + +### Strict No LLM / No AI Policy + +- No LLMs for issues. +- No LLMs for pull requests. + +This project was created as a learning excerise, I (freshollie) like learning new things. Of course it also has real world outcomes (having the ability to play, mod, port this game), but we as maintainers are not interested in submissions from LLM origin. + +We want humans to use this project to practice their skills. Reverse engineering, documenting, understanding, being part of the process. If you submit a pull request we want you to have accountability as well as knowledge of what you've done. We want you to develop on those skills and continue to contribute. If "the computer" did all the work for you, we are not interested. + +Our policy is based off [Zig's](https://ziglang.org/code-of-conduct/) + +### Layout +``` +├── asm +│ ├── macros # Common macros used in asm files +│ └── non_matching # Non matching assembly pulled in the C compilation process +├── chao_garden # The tiny chao garden project (based on the SA2 version) +├── data # Raw data pulled into the rom via ASM files +├── graphics # Graphics related assets +├── include # Headers +├── libagbsyscall # The agbsyscall library included as part of the nintendo SDK +├── multi_boot # Any multiboot roms (used for singlepak multiplayer) +├── scripts # Scripts used in the decompilation process +├── sound # Sound related assets +├── src +│ ├── data # C files which pull in assets as part of the compilation process +│ ├── game +│ │ ├── sa1 +│ │ ├── sa2 +│ │ └── shared # C files used across all 3 games +│ ├── lib # libraries which were originally statically linked +│ │ ├── agb_flash +│ │ └── m4a +│ ├── platform # port related code +│ └── . # src root contains Dimps "engine" C files +└── tools + ├── BriBaSA_ex + ├── _shared + ├── agbcc + ├── aif2pcm + ├── entity_positions + ├── gbafix + ├── gbagfx + ├── mid2agb + ├── preproc + └── scaninc +``` + +### Notable info + +- The [Kirby & The Amazing Mirror](https://github.com/jiangzhengwenjz/katam/) decompilation uses the same "engine", as it was written by the same dev team (Dimps) +- https://decomp.me is a great resource for helping to create matching function - For more info, see the [FAQs section](https://zelda64.dev/games/tmc) of TMC ## Credits -- [JaceCear](https://github.com/JaceCear) for his dedication to understanding the internals of the graphics engine, writing [tools to extract this data](https://github.com/JaceCear/SA-Trilogy-Animation-Exporter), as well as massive effort in contributing towards the decompilation process, *and* setting up the PC ports +- [JaceCear](https://github.com/JaceCear) for his dedication to understanding the internals of the graphics engine, writing [tools to extract this data](https://github.com/JaceCear/SA-Trilogy-Animation-Exporter), as well as massive effort in contributing towards the decompilation process (including decompiling the whole of sa1!), *and* setting up the PC ports - Shout out to [@froggestspirit](https://github.com/froggestspirit) for the drive to set this project up - Special thanks to [@normmatt](https://github.com/normmatt) for the initial repo setup and sounds disassembly - - [Pokemon Reverse Engineering Tools](https://github.com/pret) community for their help with the project, and tooling for GBA decompilations - [Kermalis](https://github.com/Kermalis) for [their tool](https://github.com/Kermalis/VGMusicStudio) which was used to dump the game midis - [琪姬](https://github.com/laqieer) for their exellent work [documenting](https://github.com/FireEmblemUniverse/fireemblem8u/pull/137) all the quirks of matching midis diff --git a/include/game/sa1/stage/stage_ui.h b/include/game/sa1/stage/stage_ui.h index 0ffbb5ab37..6817e4062a 100644 --- a/include/game/sa1/stage/stage_ui.h +++ b/include/game/sa1/stage/stage_ui.h @@ -40,8 +40,7 @@ typedef struct { } StageUI_20; typedef struct { - /* 0x00 */ u8 digitsRings[3]; - /* 0x03 */ u8 filler3[0x5]; + /* 0x00 */ u8 digitsRings[8]; /* 0x08 */ s16 unk8; /* 0x0A */ s16 unkA; /* 0x0C */ s16 unkC; diff --git a/include/game/shared/stage/collision.h b/include/game/shared/stage/collision.h index 3844ca6641..997365df4a 100644 --- a/include/game/shared/stage/collision.h +++ b/include/game/shared/stage/collision.h @@ -63,7 +63,7 @@ EHit Coll_Player_Boss_1(Sprite *s, CamCoord worldX, CamCoord worldY, Player *p); EHit Coll_Player_Bosses_2_6(Sprite *s, CamCoord worldX, CamCoord worldY, Player *p); EHit sub_800BF10(Sprite *s, CamCoord worldX, CamCoord worldY, Player *p); EHit sub_800BFEC(Sprite *s, CamCoord worldX, CamCoord worldY, Player *p); -u32 sub_800C0E0(Sprite *s, CamCoord screenX, CamCoord screenY, Player *p); +EHit sub_800C0E0(Sprite *s, CamCoord screenX, CamCoord screenY, Player *p); #endif u32 Coll_Player_Entity_Intersection(Sprite *s, CamCoord x, CamCoord y, Player *p); diff --git a/src/game/sa1/stage/enemies/boss_5_mecha_knuckles.c b/src/game/sa1/stage/enemies/boss_5_mecha_knuckles.c index 537fdefaf0..946fade332 100644 --- a/src/game/sa1/stage/enemies/boss_5_mecha_knuckles.c +++ b/src/game/sa1/stage/enemies/boss_5_mecha_knuckles.c @@ -193,7 +193,7 @@ bool32 sub_804FC78(MechaKnuckles *boss, Player *p); bool32 sub_804FCA4(MechaKnuckles *boss, Player *p); bool32 sub_804FCEC(MechaKnuckles *boss, Player *p); bool32 sub_804FD54(MechaKnuckles *boss, Player *p); -bool32 sub_804ED80(MechaKnuckles *boss, Player *p); +MechaKnucklesStates sub_804ED80(MechaKnuckles *boss, Player *p); typedef struct ProcDataBoss5 { MechaKnuxPlayerFn knuxPlayerFn; diff --git a/src/game/sa1/stage/interactables/forced_slide.c b/src/game/sa1/stage/interactables/forced_slide.c index daa8f798b0..5af9ea68d4 100644 --- a/src/game/sa1/stage/interactables/forced_slide.c +++ b/src/game/sa1/stage/interactables/forced_slide.c @@ -2,6 +2,8 @@ #include "core.h" #include "malloc_vram.h" #include "lib/m4a/m4a.h" + +#include "game/globals.h" #include "game/shared/stage/entity.h" #include "game/sa1/stage/player_controls.h" #include "game/shared/stage/player.h" @@ -195,6 +197,12 @@ void Task_ForcedSlide(void) if (IS_OUT_OF_CAM_RANGE(x, y)) { s32 i = 0; do { +#ifdef BUG_FIX + // This isn't really needed but it makes the compiler happy on some platforms + if (i > NUM_SINGLEPLAYER_CHARS_MAX - 1) { + break; + } +#endif if (slide->unk3D[i] != 0) { m4aSongNumStop(SE_201); diff --git a/src/game/sa1/stage/stage_ui.c b/src/game/sa1/stage/stage_ui.c index 07bc931bf3..223ee2a472 100644 --- a/src/game/sa1/stage/stage_ui.c +++ b/src/game/sa1/stage/stage_ui.c @@ -125,6 +125,12 @@ NONMATCH("asm/non_matching/game/sa1/stage/stage_ui__Task_StageUIMain.inc", void remainder = gLevelScore; for (i = 8; i >= 0; i--) { +#ifdef BUG_FIX + // How did this even happen? + if (i >= ARRAY_COUNT(ui->digitsRings)) { + continue; + } +#endif remainder0 = Div(remainder, 10); ui->digitsRings[i] = UI_DIGIT(remainder - ((remainder0 << 3) + (remainder0 << 1))); @@ -441,7 +447,7 @@ NONMATCH("asm/non_matching/game/sa1/stage/stage_ui__Task_SpecialStageUIMain.inc" } else { // _08053DF0 remainder = gSpecialStageCollectedRings; - for (i = 0; i < (s32)ARRAY_COUNT(ui->digitsRings); i++) { + for (i = 0; i < 3; i++) { remainder0 = Div(remainder, 10); ui->digitsRings[2 - i] = UI_DIGIT(remainder - ((remainder0 << 3) + (remainder0 << 1))); diff --git a/src/game/sa1/stage/stage_ui_z.c b/src/game/sa1/stage/stage_ui_z.c index d9bd7ee035..f724ecabe1 100644 --- a/src/game/sa1/stage/stage_ui_z.c +++ b/src/game/sa1/stage/stage_ui_z.c @@ -757,7 +757,7 @@ void Task_8055B18() { // TODO: Inline the mem calls! // const u8 arr0[16] = { /* data here */ }; - const u8 arr0[22 + 5]; + u8 arr0[22 + 5]; memcpy((void *)arr0, &gUnknown_086883CC[0], sizeof(arr0) - 5); memset((void *)(arr0 + 22), 0, 5); diff --git a/src/game/shared/stage/demo_manager.c b/src/game/shared/stage/demo_manager.c index 96bffc254b..08e6b1d04d 100644 --- a/src/game/shared/stage/demo_manager.c +++ b/src/game/shared/stage/demo_manager.c @@ -62,8 +62,8 @@ void CreateDemoManager(void) DemoManager *dm; Sprite *s; #if (GAME == GAME_SA1) - const AnimId arr[2]; - memcpy((void *)&arr, gPressStartTiles, sizeof(arr)); + AnimId arr[2]; + memcpy(&arr, gPressStartTiles, sizeof(arr)); #endif blendCtrl = gBldRegs.bldCnt & 0xC0; #if (GAME == GAME_SA2) From 13d75993b6349073996b9cf6499b27b176a54af1 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Wed, 6 May 2026 00:08:40 +0100 Subject: [PATCH 2/5] ci fix --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 71d685f4f1..527297dc15 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -274,6 +274,8 @@ jobs: - name: Checkout if: ${{ github.event_name == 'push' }} uses: actions/checkout@master + - name: Fetch assets + run: curl -o baserom_${{ matrix.game }}.gba -u ${{ secrets.STORAGE_USERNAME }}:${{ secrets.STORAGE_PASSWORD }} ${{ secrets.STORAGE_BASE_URL }}/${{ matrix.game }}/baserom.gba - name: Install tools (Linux) if: ${{ matrix.os == 'ubuntu-24.04' }} From 0277b50926614b8d558c7dc17f0f1ec04b81302d Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Wed, 6 May 2026 00:21:21 +0100 Subject: [PATCH 3/5] fixes --- include/core.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/include/core.h b/include/core.h index 0c4a96f537..948400514e 100644 --- a/include/core.h +++ b/include/core.h @@ -296,13 +296,8 @@ extern u16 gDispCnt; extern winreg_t gWinRegs[6]; extern struct BlendRegs gBldRegs; extern BgAffineReg gBgAffineRegs[NUM_AFFINE_BACKGROUNDS]; -#if (GAME == GAME_SA1) -extern ColorRaw gObjPalette[OBJ_PLTT_SIZE / sizeof(u16)]; -extern ColorRaw gBgPalette[BG_PLTT_SIZE / sizeof(u16)]; -#else extern ColorRaw gObjPalette[16 * PALETTE_LEN_4BPP]; extern ColorRaw gBgPalette[16 * PALETTE_LEN_4BPP]; -#endif extern u16 gBgCntRegs[4]; // TODO: Turn this into a struct-array? From 7a284a343ea4cd5633156a69b535e3b27ac80e15 Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 7 May 2026 21:22:01 +0100 Subject: [PATCH 4/5] fix readme --- README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index 26b16b0e30..88aa960483 100644 --- a/README.md +++ b/README.md @@ -92,17 +92,7 @@ Our policy is based off [Zig's](https://ziglang.org/code-of-conduct/) │ │ └── m4a │ ├── platform # port related code │ └── . # src root contains Dimps "engine" C files -└── tools - ├── BriBaSA_ex - ├── _shared - ├── agbcc - ├── aif2pcm - ├── entity_positions - ├── gbafix - ├── gbagfx - ├── mid2agb - ├── preproc - └── scaninc +└── tools # tools used for compilation or extraction ``` ### Notable info From 497ddcb7be1902478430490fca5d36a31e4d270c Mon Sep 17 00:00:00 2001 From: Oliver Bell Date: Thu, 7 May 2026 21:36:02 +0100 Subject: [PATCH 5/5] remove ps2 folder --- Makefile | 10 ++++------ ps2/sa1/ntsc/SYSTEM.CNF | 3 --- ps2/sa2/ntsc/SYSTEM.CNF | 3 --- 3 files changed, 4 insertions(+), 12 deletions(-) delete mode 100644 ps2/sa1/ntsc/SYSTEM.CNF delete mode 100644 ps2/sa2/ntsc/SYSTEM.CNF diff --git a/Makefile b/Makefile index 3dd14a121d..3a78e63b67 100644 --- a/Makefile +++ b/Makefile @@ -541,13 +541,11 @@ else endif -ifeq ($(PLATFORM),gba) $(ROM): $(ELF) libagbsyscall +ifeq ($(PLATFORM),gba) $(OBJCOPY) -O binary --pad-to 0x8400000 $< $@ $(FIX) $@ -p -t"$(TITLE)" -c$(GAME_CODE) -m$(MAKER_CODE) -r$(GAME_REVISION) --silent -else -$(ROM): $(ELF) libagbsyscall -ifeq ($(PLATFORM),win32) +else ifeq ($(PLATFORM),win32) $(OBJCOPY) -O pei-x86-64 $< $@ else ifeq ($(PLATFORM),sdl) cp $< $@ @@ -560,13 +558,13 @@ else ifeq ($(PLATFORM),sdl_psp) @-rm -f $(BUILD_NAME).psp_strip.elf else ifeq ($(PLATFORM),ps2) @echo Creating $(ROM) from $(ELF) - @cp -r ps2/$(BUILD_NAME)/ntsc $(OBJ_DIR)/iso + @mkdir -p $(OBJ_DIR)/iso + @printf "BOOT2 = cdrom0:\\$(PS2_GAME_CODE);1\nVER = 1.00\nVMODE = NTSC" > $(OBJ_DIR)/iso/SYSTEM.CNF @cp $< $(OBJ_DIR)/iso/$(PS2_GAME_CODE) @mkisofs -o $(ROM) $(OBJ_DIR)/iso/ else $(OBJCOPY) -O pei-x86-64 $< $@ endif -endif # Build c sources, and ensure alignment $(C_BUILDDIR)/%.o: $(C_SUBDIR)/%.c diff --git a/ps2/sa1/ntsc/SYSTEM.CNF b/ps2/sa1/ntsc/SYSTEM.CNF deleted file mode 100644 index f8a4a424ab..0000000000 --- a/ps2/sa1/ntsc/SYSTEM.CNF +++ /dev/null @@ -1,3 +0,0 @@ -BOOT2 = cdrom0:\SLUS_054.01;1 -VER = 1.00 -VMODE = NTSC \ No newline at end of file diff --git a/ps2/sa2/ntsc/SYSTEM.CNF b/ps2/sa2/ntsc/SYSTEM.CNF deleted file mode 100644 index 9c440527d2..0000000000 --- a/ps2/sa2/ntsc/SYSTEM.CNF +++ /dev/null @@ -1,3 +0,0 @@ -BOOT2 = cdrom0:\SLUS_054.02;1 -VER = 1.00 -VMODE = NTSC \ No newline at end of file