Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added data/.DS_Store
Binary file not shown.
Binary file modified data/DIGISND1.DAT
Binary file not shown.
Binary file modified data/DIGISND2.DAT
Binary file not shown.
Binary file modified data/DIGISND3.DAT
Binary file not shown.
Binary file added data/FAT.DAT
Binary file not shown.
Binary file added data/IBM_SND1.DAT
Binary file not shown.
Binary file added data/IBM_SND2.DAT
Binary file not shown.
Binary file added data/KID.DAT
Binary file not shown.
Binary file added data/LEVELS.DAT
Binary file not shown.
Binary file modified data/MIDISND1.DAT
Binary file not shown.
Binary file modified data/MIDISND2.DAT
Binary file not shown.
Binary file added data/PRINCE.DAT
Binary file not shown.
Binary file added data/PV.DAT
Binary file not shown.
Binary file added data/SHADOW.DAT
Binary file not shown.
Binary file added data/SKEL.DAT
Binary file not shown.
Binary file added data/TITLE.DAT
Binary file not shown.
Binary file added data/VDUNGEON.DAT
Binary file not shown.
Binary file added data/VIZIER.DAT
Binary file not shown.
Binary file added data/VPALACE.DAT
Binary file not shown.
Binary file added gba/.DS_Store
Binary file not shown.
118 changes: 118 additions & 0 deletions gba/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#---------------------------------------------------------------------------------
# SDLPoP for Game Boy Advance (devkitARM)
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "DEVKITARM is not set. Install devkitPro/devkitARM and source devkitarm.")
endif

include $(DEVKITARM)/gba_rules

#---------------------------------------------------------------------------------
TARGET := SDLPoP_gba
BUILD := build
SOURCES := src ../src
INCLUDES := include ../src
DATA := data
ASSETS := assets

GAME_TITLE := SDLPOP
GAME_CODE := SLPP
MAKER_CODE := PP
GAME_VER := 0

#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -mthumb -mthumb-interwork
CFLAGS := -g -Wall -O2 -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer \
-ffast-math -fno-strict-aliasing \
$(ARCH)
CFLAGS += $(INCLUDE) -DARM9 -D__GBA__ -DGBA -DPOP_GBA

CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS := -g $(ARCH) -Wl,-Map,$(notdir $*.map) \
-L$(CURDIR) -B$(CURDIR) \
-Wl,--wrap=free \
-Wl,--wrap=malloc -Wl,--wrap=calloc -Wl,--wrap=realloc \
-Wl,--wrap=snprintf -Wl,--wrap=vsnprintf \
-Wl,--wrap=sprintf -Wl,--wrap=vsprintf

LIBS := -lmm -lgba -lm
LIBDIRS := $(LIBGBA)

# gba_rules invokes $(LD) with gcc-style flags + -specs=gba.specs, so LD must
# be the gcc driver, not bare ld.
LD := $(CC)

#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
$(foreach dir,$(ASSETS),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)

CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) \
$(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.bin)))

# Exclude SDLPoP source files that we replace or that pull in dependencies
# we can't satisfy on GBA. The rest of the upstream code compiles unchanged.
EXCLUDE_CFILES := main.c midi.c opl3.c lighting.c screenshot.c \
replay.c stb_vorbis.c
CFILES := $(filter-out $(EXCLUDE_CFILES),$(CFILES))

export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES)))

export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)

.PHONY: $(BUILD) clean all

#---------------------------------------------------------------------------------
all: $(BUILD)

$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile

clean:
@echo Cleaning...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba $(TARGET).sav

#---------------------------------------------------------------------------------
else

DEPENDS := $(OFILES:.o=.d)

$(OUTPUT).gba : $(OUTPUT).elf
$(SILENTCMD)$(OBJCOPY) -O binary $< $@
$(SILENTCMD)gbafix $@ -t$(GAME_TITLE) -c$(GAME_CODE) -m$(MAKER_CODE) -r$(GAME_VER)
@echo built ... $(notdir $@)

# Override the default link rule: we use our own linker script that puts
# .data / .bss in EWRAM, and our own specs so the stock gba_cart.ld is not
# picked up (which would conflict and force .data into IWRAM).
$(OUTPUT).elf : $(OFILES)
$(SILENTMSG) linking cartridge
$(SILENTCMD)$(LD) $(LDFLAGS) -B$(CURDIR)/.. -specs=$(CURDIR)/../gba_ewram.specs \
$(OFILES) $(LIBPATHS) $(LIBS) -o $@

$(OFILES_SRC) : $(HFILES)

%_bin.h %.bin.o : %.bin
@echo $(notdir $<)
$(SILENTCMD)$(bin2o)

-include $(DEPENDS)

endif
#---------------------------------------------------------------------------------
153 changes: 153 additions & 0 deletions gba/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# SDLPoP — Game Boy Advance port (devkitPro/devkitARM)

This is a port of [SDLPoP](https://github.com/NagyD/SDLPoP) (Prince of Persia)
to the Nintendo Game Boy Advance, using **devkitARM** + **libgba** +
**maxmod**.

It is built from the upstream sources with:

- a **SDL2 compatibility shim** (`gba/include/SDL2/SDL.h`,
`gba/src/sdl_shim.c`) covering the surface, palette, rwops, event, timing
and audio APIs that SDLPoP touches;
- **direct hardware drivers** for Mode-4 video (`gba_video.c`), DirectSound A
PCM audio (`gba_audio.c`), buttons (`gba_input.c`), 60 Hz VBlank ticking
(`gba_time.c`), 32 KB SRAM saves (`gba_save.c`), and ROM-resident data
blobs (`gba_file.c`);
- **`#ifndef __GBA__`** guards around a handful of subsystems that won't fit
on a 16.78 MHz ARM7 with 256 KB EWRAM (OPL3, MIDI, stb_vorbis, lighting,
screenshot, menu, replay, quicksave, fade, fast-forward, debug cheats,
dark transition, teleports).

The unmodified upstream source files in `../src` compile as-is once
`__GBA__` is defined and the SDL include path is the shim.

## Limitations vs. desktop SDLPoP

- **Resolution**: 240×160 (GBA hardware) vs. 320×200 (original). A camera
scroll keeps the prince in view. The original game world still runs at
320×192 internally.
- **Audio**: digitized SFX only (DIGISND*.DAT), played as 8-bit PCM via
DirectSound A on DMA1 at 18 157 Hz. Adlib music, PC-speaker beeps, MIDI
and OGG music are **dropped**. You can optionally prerender music as
raw 8-bit signed PCM and feed it via `gba_music_play()` from a mod.
- **Save**: the original `PRINCE.SAV` file is redirected to cartridge SRAM
(32 KB). Emulators persist this as `SDLPoP_gba.sav`.
- **No menu / replay / screenshot / lighting / fade / fast-forward / debug
cheats**: dropped to save code size and RAM.
- **Hall of Fame**: writes to SRAM (same blob namespace as save).

## Build prerequisites

1. **devkitPro** with the `gba-dev` group:
```bash
# macOS (with Homebrew):
brew install --cask devkitpro/devkitpro/devkitpro-pacman
sudo dkp-pacman -S gba-dev
# Linux: see https://devkitpro.org/wiki/Getting_Started
```
2. Source the devkitARM environment:
```bash
source /opt/devkitpro/devkitarm.sh # or wherever you installed it
```
3. Have a copy of the **Prince of Persia v1.0/1.1 DOS data files**
(`KID`, `PRINCE`, `TITLE`, `LEVELS`, `GUARD*`, `V*`, `DIGISND*.DAT`,
etc.). The SDLPoP source ships them in `../data/`.

## Build

```bash
cd gba
./tools/prep_data.sh # copies ../data/* -> data/*.bin
make
```

Produces `SDLPoP_gba.gba`, ready to run in any GBA emulator (mGBA, no$gba,
VBA-M) or to flash to a flashcart.

## Layout

```
gba/
├── Makefile # devkitARM build
├── README.md # this file
├── include/
│ ├── SDL2/SDL.h # minimal SDL2 shim header (used in place of real SDL2)
│ ├── SDL2/SDL_image.h # IMG_Load returns NULL
│ └── gba_port.h # cross-module bindings
├── src/
│ ├── sdl_shim.c # SDL_Surface, blits, palette, RWops, audio, timing
│ ├── gba_main.c # entry point (main): init libgba, call pop_main()
│ ├── gba_video.c # Mode-4 framebuffer + viewport panning
│ ├── gba_audio.c # DMA1 8-bit PCM mixer (6 voices)
│ ├── gba_sound_bridge.c # overrides SDLPoP's play_sound_from_buffer
│ ├── gba_input.c # KEYINPUT -> SDL scancodes
│ ├── gba_time.c # VBlank tick counter + SDLPoP frame timer
│ ├── gba_file.c # fopen/fread over linked DAT blobs, save -> SRAM
│ ├── gba_save.c # SRAM read/write + Fletcher-32 checksum
│ └── gba_viewport.c # camera follows the prince
├── tools/
│ └── prep_data.sh # copies ../data/* to data/*.bin
└── data/ # populated by prep_data.sh
```

## Source modifications

Three upstream files have **small** `__GBA__`-gated additions:

- `src/common.h` — preprocessor macros for `fopen`/`fread`/`stat` (re-route
to `gba_fopen` etc).
- `src/config.h` — disable `USE_LIGHTING`, `USE_SCREENSHOT`, `USE_MENU`,
`USE_REPLAY`, `USE_QUICKSAVE`, `USE_FADE`, `USE_DARK_TRANSITION`,
`USE_TELEPORTS`, `USE_AUTO_INPUT_MODE`, `USE_FAST_FORWARD`,
`USE_DEBUG_CHEATS` on `__GBA__`. The fixes are also gated by
`DISABLE_ALL_FIXES`.
- `src/seg009.c` — wraps the SDL audio implementation (`#ifndef __GBA__`)
and the SDL_Renderer-based `set_gr_mode`/`update_screen` with an `#else`
branch that creates an 8 bpp paletted surface and feeds it to
`gba_present_surface()`.

The other ~33 000 lines of upstream source compile unmodified.

## Controls

| GBA button | SDL scancode | Action |
|------------|--------------|-----------------------|
| D-Pad | Arrow keys | Move / jump / crouch |
| A | Shift | Grab / draw sword |
| B | Space | (unused / pause) |
| L | Ctrl | Chop / fight strike |
| R | Alt | Modifier |
| Start | Enter | Advance dialog |
| Select | Backspace | Cancel |

## What is NOT yet wired

- **Music**: only SFX is fed to the mixer. To add music, place a raw 8-bit
signed PCM stream as `data/music.bin` and call
`gba_music_play(music_bin, music_bin_size, 1)` at the start of each
level (see `gba_audio.c`). A music conversion pipeline isn't included.
- **Sound resampling**: digitized SFX is played at the rate stored in the
DIGISND header; the mixer plays at a fixed 18 157 Hz, so pitch is
approximate. To improve, add a nearest-neighbour resampler in
`play_digi_sound()` in `gba_sound_bridge.c`.
- **The `chtab` sprite cache** is loaded into EWRAM on demand by upstream
code. With KID + PRINCE + GUARD + VPALACE + VDUNGEON simultaneously
active you can exceed 256 KB; if you hit OOM crashes, see
`load_chtab_from_file` in `seg009.c` and consider releasing earlier
chtabs or moving them into IWRAM/ROM.
- **Lighting**, **fade**, **dark room transition** — disabled. If you want
fade-in/out, re-enable `USE_FADE` and live with a slower CPU budget.

## Troubleshooting

- `gba.h: file not found` from your IDE — your editor is finding the host
SDL2/clang headers, not devkitARM's. Build with `make`, not the IDE.
- `undefined reference to '_kid_bin_size'` at link time — you didn't run
`./tools/prep_data.sh`, so the linker has no blobs to attach. Run it
and rebuild.
- The ROM boots to a black screen — typical first-run hang because some
sprite chtab failed to load. Try with the original `data/` files only,
no mods.
- Saves don't persist in your emulator — make sure the emulator is
configured to write `.sav` files alongside the ROM (mGBA does by
default; some emulators need `--sram`).
Binary file added gba/SDLPoP_gba.gba
Binary file not shown.
Loading