A progressive C reimplementation of parts of the CPS3 BIOS, cross-compiled
with sh-elf-gcc for the SH-2 processor.
Everything here -- addresses, data structures, task/scheduler behavior, flash protocols -- was recovered through Ghidra-based reverse engineering of the original Capcom BIOS and the UltraBIOS; none of it comes from official documentation or source.
This is not a full BIOS rewrite: the large majority of the original Capcom BIOS binary is kept untouched. Specific points (task-table slots, function pointers, individual instructions) are patched with hooks that redirect execution into C code; that C code runs its own logic and then either returns to the original flow or dispatches into another original BIOS routine/task -- custom C and original BIOS code coexist and call into each other in the same running image.
Target: dead-cart CPS3 hardware (key1=0, key2=0) and MAME emulation.
The C-BIOS replaces the BIOS MasterInitializer task with its own menu loop.
The menu only appears when there is no game installed on the SIMM banks, or
when START is held during boot; otherwise the system auto-boots straight into
the installed game (see Boot Flow):
| Item | Action |
|---|---|
| 1. RUN GAME (CD CHECK) | Boot installed game with full CD verification |
| 2. BOOT GAME | Boot installed game directly (no CD, SIMM-only) |
| 3. BOOT GAME REGION SELECT | Boot with region selection screen |
| 4. REWRITE NEW GAME | Flash a new game from CD to SIMMs |
| 5. SECURITY CASSETTE UTILITY | Original BIOS cassette/flash menu |
| 6. ABOUT | Version info |
Auto-boot, 2. BOOT GAME, and boot-with-region-select all dispatch into
BIOS_TASK_BOOT_GAME, the same direct-boot task the original BIOS used
internally -- it boots straight from the SIMM banks without going through
the CD verification task (BIOS_TASK_CD_VERIFY, used only by
1. RUN GAME (CD CHECK)). No CD drive/disc needs to be present for these
paths. Since this direct-boot behavior is reachable from the menu on any
build, a separately patched "NOCD" firmware variant is not needed -- the same
binary supports both CD-present and CD-absent operation.
Every screen shows its available actions in the footer, following the original BIOS convention:
| Hint | Meaning |
|---|---|
SELECT = 1P UP or DOWN |
Move the cursor |
CHANGE = 1P LEFT or RIGHT |
Change the highlighted value |
START = 1P SHOT1 |
Confirm / select |
BACK = 1P SHOT2 |
Go back one screen |
Selects a game from the embedded game list, then opens a per-game config screen before writing:
| Option | Effect |
|---|---|
| REWRITE SIMM 1-6 | Toggle whether each SIMM bank is written -- turn off the banks you don't need to touch so the rewrite only writes the selected ones instead of waiting through a full rewrite of all banks |
| SKIP ERASE | [NOR] default (NOR-aware: skips erase for NOR replacement chips, normal original-BIOS erase for Capcom chips) / [YES] (skip for all chips) / [NO] (sector-erase NOR chips first) |
| SKIP VERIFY | [NOR] default (NOR-aware: skips verify for NOR replacement chips, normal original-BIOS verify for Capcom chips) / [YES] (skip for all chips) / [NO] (verify via original BIOS) |
Writes the selected game from CD to the SIMM banks. NOR replacement chips (Spansion S29GL128S, Micron MT28EW) are written via a custom AMD write-buffer implementation; original Capcom chips fall back to the original BIOS write routine.
This flow keeps the UltraBIOS memory-address patches, so any game in the embedded game table can be loaded dynamically at runtime instead of being hardcoded to a single title like the stock Capcom BIOS.
REWRITE NEW GAME reads standard UltraBIOS CDs directly, with two exceptions: SFIII: 3rd Strike MakotoFix and SFIII: 4th Strike are not distributed as standalone UltraBIOS CDs, so their ISO needs to be rebuilt first with the matching script from sfiii4s-custom.
The existing Capcom chip types are not patched or modified -- their original identify/write/erase behavior is untouched. Support for NOR replacement chips is added on top, as new detection cases and dedicated write/erase routines, so both worlds coexist without changing how the original chips are handled. This is additive by design: identifying and handling further chip types (beyond Spansion/Micron) only requires adding new JEDEC ID cases and, if the protocol differs, a new write/erase routine -- no existing chip support needs to change.
Original Capcom chips and NOR replacements are detected and handled:
| Type | Chip | Wiring |
|---|---|---|
| Fujitsu MBM29F400 | Original | AMD-compat |
| Intel / Sharp | Original | Intel protocol |
| Hitachi HM538123 | Original | WriteConst1 |
| MXIC/Macronix | Original | Own protocol |
| Spansion S29GL128S (NOR) | NOR replacement | AMD-compat, NOR-wired |
| Micron MT28EW (NOR) | NOR replacement | AMD-compat, NOR-wired |
| Micron MT28EW (DS) | NOR replacement | AMD-compat, DS-wired |
NOR replacement chips are wired so a physical HW dipswitch selects which slot is visible to the system -- multiple games can live on the same chip, one per slot. A practical way to load several games:
- From 5. SECURITY CASSETTE UTILITY -> FLASH MEMORY, erase the whole chip once.
- Go to 4. REWRITE NEW GAME and write the first game, leaving
SKIP ERASE at its default (
[NOR]) -- since the chip is already blank, this skips the sector-erase step and saves that time. - When the write finishes, power off the system.
- Flip the HW dipswitch to the next slot.
- Power back on and repeat REWRITE NEW GAME for the next game -- still with SKIP ERASE at default, since that slot is blank too.
sh-elf-gcc 13.2.0 + binutils 2.42, targeting SH-2 big-endian.
The build runs inside a container -- you only need Podman (or Docker):
# macOS
brew install podman
# Verify
podman --versionThe ./docker-make wrapper handles the rest, including the first-time
toolchain image build (~25 minutes, one time only).
Required for the patching scripts. Any Python 3.8+ works.
The build patches sfiii3_euro.29f400.u2 (Street Fighter III 3rd Strike,
Euro). This proprietary Capcom binary is not included in the repository
-- place your own dump in the project root before building.
./docker-make # compile + link + patch + deploy to MAME (USA region by default)
./docker-make REGION=3 # same, EURO region
./docker-make clean # remove build artifacts
./docker-make info # symbol-by-symbol disassemblyThe REGION parameter sets the region byte embedded in both output binaries:
| Value | Region |
|---|---|
| 1 | JAPAN |
| 2 | ASIA |
| 3 | EURO |
| 4 | USA |
| 5 | HISPANIC |
| 6 | BRAZIL |
| 7 | OCEANIA |
| 8 | ASIA NCD |
The region is applied to the shared intermediate binary before the standard and custom variants are derived, so both outputs always carry the same region.
Output files:
| File | Description |
|---|---|
build/cps3_cbios_standard.u2 |
Standard SH2 binary (unencrypted, for MAME cps3boot) |
build/cps3_cbios_custom.u2 |
Custom SH2 binary (encrypted, for MAME cps3boota) |
If MAME ROM directories are mounted (see docker-make), both files are
deployed automatically.
mame cps3boot -bios 0 -debugLoad build/cps3_cbios_standard.u2 as the flash ROM
(no-battery_bios_29f400_for_hd6417095_sh2.u2).
For the dead-cart / encrypted variant (cps3boota), use build/cps3_cbios_custom.u2.
Note: mame resolves roms/, cfg/, nvram/, etc. relative to its own
working directory, so run it from wherever your MAME install expects those
folders rather than from the project directory.
reset -> BIOS phases 1-8 (original Capcom code)
-> phase 9 stub @ 0x20000 -> bridge to 0x498
-> phase 10 stub @ 0x2028 -> C runtime init (BSS zero + data copy)
-> phase 11 stub @ 0x1B00 -> PR = cps3_kernel_main, RTS
-> cps3_kernel_main -> jumps to BIOS scheduler entry @ 0xEC8
-> task[0] = cps3_master_init (replaces MasterInitializer)
Only phases 9-11 run as C; phases 1-8 still run as unmodified original Capcom
BIOS code. src/crt0.s (a phase 1 replacement) exists in the tree but is not
part of the active build (see ASM_SRC in the Makefile).
The scheduler is intercepted at task[0]. Tasks [1..14] (CD verify, flash
write, region menu, etc.) are dispatched by returning the appropriate state
from that hook -- the BIOS scheduler calls them normally.
The menu is not shown unconditionally. cps3_master_init runs the original
HW init first, then decides between auto-boot and the menu:
cps3_master_init:
1. BIOS_MASTER_INIT() -- original HW init (font, tilemap, ISRs)
2. Pre-load REGION_SELECTED_BYTE from flash (fallback for every boot path)
3. Re-entry check (SKIP_MENU_FLAG set by a prior menu action, e.g. after
REWRITE's CRT0 restart) -> return immediately, let the BIOS scheduler
continue into the requested task (no menu, no input read)
4. Read input once. If START is NOT held AND a game is already written
to the SIMM banks (header check against flash) -> auto-boot directly
into BIOS_TASK_BOOT_GAME, skipping the menu entirely
5. Otherwise (no game installed, OR START held at power-on to force
maintenance mode) -> enter the C-BIOS main menu loop
So in practice: a cabinet with a game already flashed boots straight into that game with no menu, unless START is held during power-on. The menu only appears by default when the SIMMs are blank/unrecognized (nothing to boot).



