Skip to content

Latest commit

 

History

721 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MiSTer OpenBOR

Hybrid ARM+FPGA OpenBOR beat-em-up engine for MiSTer FPGA. Ships two engine builds (4086 and 7533) that share a single PAK library, the same controller mappings, and identical FPGA video/audio output. Pick which build to load at runtime by selecting the matching RBF in _Other/. Inspired by SumolX's original MiSTer OpenBOR port.

Which build do I load?

Both builds run on the same FPGA core, take the same controllers, output the same Genesis-H40-exact video, and load PAKs from the same games/OpenBOR/Paks/ folder. The only difference is the OpenBOR engine version inside each ARM binary.

Build Best for PAK era Engine
OpenBOR_4086 Legacy community PAK collections (~300 games on Archive.org / Retrobat / Batocera / Launchbox) OpenBOR 3.x — Builds 3366 to 4086 OpenBOR v3.0 Build 4086 (commit af23dc9c), SDL 1.2.15
OpenBOR_7533 Modern PAK collections — TMNT: Rescue-Palooza, Final Fight LNS Ultimate, Avengers UBF v2.7+, Zvitor / RVGM sets, Pocket Dimensional Clash 2, He-Man and the Masters of the Universe OpenBOR 4.0 — Builds 6000+ era, also runs older PAKs backward-compatibly OpenBOR 4.0 Build 7533 (tag v7533), SDL 2.0.8

If a PAK won't run on one build, reload the other RBF and try again — your Paks/ folder is shared, no file moves needed.

Features

  • Native FPGA video output — 320×224 @ 59.92 Hz with exact Sega CD NTSC pixel clock (6.712 MHz from NTSC colorburst crystal). H40+V28 mode — CRT image width matches NES/SNES/Genesis exactly (47.68 µs active time)
  • Direct DDR3 write frame path — engine's video_copy_screen writes pixel data directly to the FPGA's video ring buffer at 0x3A000000, bypassing SDL's renderer/surface chain entirely (saved ~15 ms/frame on Cortex-A9; lifted native fps from ~29 to ~85-100 on a powerful frame-present path 2026-05-22)
  • Per-frame entity-collision + AI fast paths (v3.0, 2026-05-26) — five mechanical optimizations in the entity-tick loop. All ZERO-behavior-change refactors (same final filter sets, just less wasted work per iteration). Combined fps lift varies by PAK; biggest on collision-bound scenes:
    • Step 14 (B+E entity-collision cull)check_entity_collision_for() is the O(N²) hot loop inside arrange_ents(). Profile data across 7 PAKs showed it consumed 28-42% of per-tick entity work (the universal bottleneck). Added: (B) skip targets that don't carry an animation->collision_entity (engine would have returned 0 anyway — just skips the function call cost); (E) cheap 256-px rect cull before calling check_entity_collision() (256 px > any reasonable single-entity hitbox extent on a 320×224 game). Expected ~85% reduction in collision-pair work.
    • Step 15 (normal_find_target cheap-first reorder) — many AI entities call normal_find_target() per think tick. Reordered the loop body so cheap field/bit tests (death-state, distance) run BEFORE expensive function calls (faction_check_is_hostile, check_range_target_all). Same filter, expensive checks skipped earlier for entities that would have been culled anyway.
    • Step 16a (do_attack invariant hoist + B-style pre-filter)checkhit() opens with 4 early-exit conditions; 3 are per-target, 1 is invariant. Hoisted the invariant out of the per-target loop + moved 3 per-target checks into the caller to skip the function call cost on non-hittable targets.
    • Step 16b (block_find_target reorder) — short-circuit && chain reordered cheap-first (same pattern as Step 15).
    • Step 16c (find_ent_here invariant hoist)self->modeldata.grabdistance * 0.83333 and /3 are invariants per call; hoisted out of the per-iteration && chain. Used by grab moves and item pickup detection.
  • PAK load-time hash-map cache (v2.9, 2026-05-24) — loadsprite() cache lookup replaced from O(N) linear scan to O(1) hash table (262144 buckets, separate chaining, DJB2 hash with inline case-folding). Phase 1 + Phase 1.1 tunes shipped. Validated load-time reductions on heavy PAKs:
    • Justice League Legacy: 213 s → 69.1 s (-68%)
    • Double Dragon Reloaded Alt: 73 s → 35 s (-52%)
    • A Tale of Vengeance: ~12 s → 1.87 s (-80%+)
    • TMNT Rescue Palooza / Avengers UBF / He-Man / PDC2: 25-50% reductions
    • [LOAD] PAK loaded in N ms printf retained at end of load_models() for power-user tracking — grep '\[LOAD\]' /media/fat/logs/OpenBOR_7533/OpenBorLog.txt
  • Loading bar clamp (v2.9, 2026-05-23) — when a PAK declares a loading bar with bsize=0 (cart-author shortcut meaning "no real bar requested") AND would otherwise show pure black during the multi-second model-cache init phase, the engine renders a default on-screen progress bar at the bottom-center (1/3 screen width, 25 px from bottom). Canonical case: Double Dragon Reloaded Alternate (previously showed ~70 sec of pure black before title screen — now shows visible progress). Gated narrowly (s == &loadingbg[0] && size_x <= 0) so PAKs with intentional on-screen bars (TMNT-RP, He-Man) or per-level bgPosi displays (Avengers UBF, Pocket Dimensional Clash 2) are unchanged — no duplicate bars.
  • Native FPGA audio output — 48 kHz stereo via DDR3 ring buffer, no ALSA. Audio kernel: nearest-neighbor (zero-order hold) at engine + wrapper (matches upstream OpenBOR engine/source/gamelib/soundmix.c FIX_TO_INT shift-truncation kernel at all three sample-read sites — music + 8-bit voice + 16-bit voice; wrapper at patches/sblaster_patch.c::audio_thread_fn mirrors the engine character — both stages NN).
  • CRT support — scanlines, shadow masks, and analog video output for CRT displays
  • MiSTer OSD integration — load PAK files from the file browser
  • 4-player support — connect up to 4 controllers, add players by pressing START
  • Custom pause menu — Continue / Options / Recording / Reset Pak / Quit. Music and sound effects pause cleanly on menu entry, resume on Continue (audio-tail leak fixed 2026-05-22)
  • Gameplay recording & replay — record a playthrough and watch it back hands-free, from the pause menu or the MiSTer OSD (.inp files); deterministic bit-for-bit playback, press any button to take over (see Recording & Replay)
  • Auto-launch — OpenBOR starts automatically when the core is loaded
  • Sub-native PAKs scale automatically — PAKs with native resolutions other than 320×224 (320×240 4086-era PAKs, 480×272 PSP-widescreen PAKs like Pocket Dimensional Clash 2, 960×480 He-Man, 480×272 Avengers UBF, etc.) are anisotropic-nearest-neighbor-squished into the 320×224 Sega CD V28 NTSC active area edge-to-edge. NN matches engine render character (engine renders pixel-exact, wrapper preserves it; bilinear was ~4× more CPU for marginal benefit). Aspect distortion is intentional — matches Sega CD displayed area.

Quick Install

The recommended path is via the MiSTer Frontier combined database, which auto-deploys both OpenBOR builds (and any other Frontier core you opt into) every time you run update_all.

Add this to /media/fat/downloader.ini on your MiSTer's SD card:

[MiSTerOrganize/MiSTer_Frontier]
db_url = https://raw.githubusercontent.com/MiSTerOrganize/MiSTer_Frontier/db/db.json.zip
filter = openbor-4086 openbor-7533

The filter line picks both OpenBOR builds. Drop the filter line entirely if you want every Frontier core, or pick just one build — see the Frontier README for the full filter list.

Filter Result
openbor-4086 openbor-7533 Both builds + shared handler/docs
openbor-4086 4086 only (with shared infra)
openbor-7533 7533 only (with shared infra)

After editing downloader.ini:

  1. Run update_all from MiSTer's Scripts menu — installs the FPGA cores, ARM binaries, unified handler, and docs
  2. Run Scripts/Install_MiSTer_Frontier.sh once — registers the Master Daemon that auto-launches the engine. Idempotent
  3. Place your .pak game modules in /media/fat/games/OpenBOR/Paks/
  4. Load either OpenBOR_4086 or OpenBOR_7533 from the MiSTer console menu — the engine launches automatically

Inspecting the manifest: DB Inspector for MiSTer_Frontier — every file, hash, size, and tag visible in the browser. Useful for verifying which files a given filter would install before you run update_all.

Manual Install

Extract the release zip to the root of your MiSTer SD card (/media/fat/):

/media/fat/
├── _Other/
│   ├── OpenBOR_4086_YYYYMMDD.rbf          FPGA core (4086 build, dated)
│   └── OpenBOR_7533_YYYYMMDD.rbf          FPGA core (7533 build, dated)
├── docs/
│   └── OpenBOR/
│       └── README.md                      This file
├── games/
│   └── OpenBOR/                           Shared folder for BOTH builds
│       ├── OpenBOR_4086                   ARM binary (4086 engine)
│       ├── OpenBOR_7533                   ARM binary (7533 engine)
│       ├── _handler.sh                    Master_Daemon dispatcher
│       └── Paks/                          Place your .pak game modules here
├── logs/
│   ├── OpenBOR_4086/                      4086 engine logs (handler + engine + script)
│   └── OpenBOR_7533/                      7533 engine logs (handler + engine + script)
├── saves/
│   ├── OpenBOR_4086/                      4086 engine saves
│   └── OpenBOR_7533/                      7533 engine saves
├── savestates/
│   ├── OpenBOR_4086/                      4086 savestates
│   └── OpenBOR_7533/                      7533 savestates
└── Scripts/
    └── Install_MiSTer_Frontier.sh         Install script (shipped by MiSTer_Frontier — unified across all Frontier cores)

Saves and savestates are kept separate between the two engine builds because the on-disk format isn't guaranteed compatible across the OpenBOR 3.x → 4.0 boundary.

Supported Features

Both OpenBOR_4086 and OpenBOR_7533 cores have identical support across these dimensions:

Feature OpenBOR_4086 OpenBOR_7533
Saves (<pak>.sav engine progress) /media/fat/saves/OpenBOR_4086/ /media/fat/saves/OpenBOR_7533/
Savestates (<pak>.scr engine snapshot) /media/fat/savestates/OpenBOR_4086/ /media/fat/savestates/OpenBOR_7533/
Logs (with auto-prune N=10) /media/fat/logs/OpenBOR_4086/ /media/fat/logs/OpenBOR_7533/
Configs (<pak>.cfg + default.cfg + <pak>.hi) /media/fat/config/ (shared across sister cores) ✅ shared with 4086
MGLs (_Other/*.mgl one-click launchers)
Gameplay recording & replay (<pak>.inp) ✅ 8 slots per PAK, reachable from the pause menu or the MiSTer OSD ("Replay Slot" + "Play Replay"), plus "Load Replay" for a file someone sent you; deterministic bit-for-bit, hands-free, press any button to take over ✅ same
Gamepad (up to 4P, Start adds player)
Keyboard ❌ no (SDL keyboard not wired through dummy driver) ❌ no
Mouse ❌ no (no native engine mouse support) ❌ no
Screen Positioning (CRT) H ±3 / V ±3
Online Network Play
Multiplayer ✅ up to 4 players (Start adds player) ✅ up to 4 players
Light Gun
Aspect Ratio (Original / Full Screen / Custom1 / Custom2)
Scale Mode (Normal / V-Int / HV-Int integer scaling)
Swap Joysticks (P1↔P2)
Stereo Mix (None / 25 / 50 / 100% channel cross-bleed)

Note -- Aspect Ratio, Scale Mode, Swap Joysticks, and (on OpenBOR) Stereo Mix shipped 2026-06-08, bringing these cores in line with mainstream MiSTer console-core OSD options.

Controls (Xbox wireless controller default mapping)

Xbox wireless OpenBOR action Notes
D-pad / Left stick Movement (4-way)
A button Jump
B button Attack (primary punch/kick)
Y button Special / grab
X button Attack2 (secondary attack)
LB (Left Bumper) Attack3 e.g. Double Dragon style switch
RB (Right Bumper) Attack4
Menu / Start Start (insert coin / pause / add player)
Xbox Guide (center) MiSTer OSD core's OSD overlay — framework-level, not per-core

CONF_STR: J1,Attack,Jump,Special,Attack2,Attack3,Attack4,Start; / jn,A,B,X,Y,L,R,Start;. MiSTer's jn extension uses SNES naming (jn A=Xbox B, jn B=Xbox A, jn X=Xbox Y, jn Y=Xbox X), so the defaults above pair jn A (Xbox B) → Attack, jn B (Xbox A) → Jump, jn X (Xbox Y) → Special, jn Y (Xbox X) → Attack2. Attack3/Attack4 (added 2026-07) sit on the shoulder bumpers -- jn L = Xbox LB -> Attack3, jn R = Xbox RB -> Attack4 -- so OpenBOR games that use all six action buttons (e.g. Ultimate/Legend of the Double Dragon shoulder-button style switching) are fully playable.

Both OpenBOR_4086 and OpenBOR_7533 use the IDENTICAL mapping — sister-core swap (4086 ↔ 7533) preserves your input config. All 4 players use the same button layout. Remap buttons from the MiSTer OSD (press F12 or the OSD button on your IO board).

Pause Menu

Press START during gameplay:

  • Continue — resume gameplay
  • Options — adjust Music Volume and SFX Volume with D-pad left/right, and toggle FPS Display
  • Recording — record and play back your gameplay (see below)
  • Reset Pak — restart the current PAK fresh
  • Quit — exit to PAK browser

Navigate with D-pad up/down. Press A to confirm, X to go back.

FPS Display

(OpenBOR_7533 build.)

Pause → OptionsFPS Display turns on a live frame-rate read-out in the bottom-right corner. D-pad left/right or the select button toggles it.

The number is colour-coded so you can read it at a glance without stopping:

colour frame rate
🔴 red below 30
🟡 yellow 30 – 59
🟢 green 60 and above

It is drawn at the console's final output resolution rather than inside the game image, so it stays sharp on every PAK — including the ones that render at much higher internal resolutions and get scaled down.

It works while recording and while playing a recording back, which is the main reason it exists: because replay is deterministic, you can record a session, play it back, and read the frame rate at every point of it — the same run, the same frames, every time. The read-out never becomes part of the .inp file; recordings store your button presses, not the picture.

It starts off every time you load a PAK. It is a display overlay, so it does appear in screenshots while it is on.

Recording & Replay

Record a playthrough and watch it back — deterministic, so a recording plays exactly what you did. There are two ways to play a recording back.

Why this is handy — and a great debugging tool. A recording replays exactly what you did, every time, and you can replay it endlessly. Great for saving and re-watching a favorite run — but also a powerful way to find and fix problems: if you hit a bug, glitch, or crash, record the run that causes it and it will reproduce the same thing on demand, hands-free — no need to remember or replay it by hand. That makes an issue easy to pin down and easy to confirm once it's fixed (replay the same recording and see if it's gone). The .inp file captures the whole run, so you can keep it or share it.

What a recording contains, before you share one

A .inp is not only your button presses — it also carries that PAK's save data: your progress (.sav), the high-score table (.hi), and the script-saves that hold unlocked characters. That is what lets someone else replay a run you recorded partway through a game.

So when you share a recording, you are sharing that PAK's save data with it — including any name or initials you entered on a high-score screen.

Only replay recordings from people you trust. Playing one restores the save data it carries before the PAK starts, and OpenBOR's script-saves are executable engine script — that is how mods persist unlocked characters, and it is why they have to be included for a replay to match. Your own saves are kept separate and are never modified, but a recording from a stranger does run their data on your machine.

Recordings are saved as .inp files in /media/fat/replays/OpenBOR_7533/ — a top-level folder alongside saves/ and savestates/, one per core. You get 8 slots per PAK, the same idea as savestates: pick a slot in the Recording menu with left/right, and Record saves into it. The menu tells you whether a slot is already used before you commit, and names the slot it replaced afterwards. Each .inp is stamped with the PAK it was recorded on and the engine version. A recording only plays on its own PAK — if you load a replay while a different PAK is loaded, it won't start (load the matching PAK first). And if a later core update changes the game logic, an old replay may drift (a note is logged) — just press any button to take over.

From the pause menu (START → Recording):

  • Record — restarts the PAK and records everything from the title screen through your play. (It records from the start so playback can reproduce the run exactly.)
  • Slot N of 8 — left/right to choose which slot Record writes and Play reads. It shows used or empty so you know before you overwrite.
  • Stop Recording — saves the recording into the chosen slot and drops you back into the game.
  • Play Recording — restarts the PAK and plays the chosen slot back hands-free, driving through the menus into the game on its own.
  • Take over any time — during playback, just press any button and the automated inputs stop instantly so you can play.

From the MiSTer OSD (a second way to reach the same eight slots):

  • Replay Slot — pick 1-8. This is the same slot the pause menu shows: move it in either place and the other follows, so you are never picking a slot in one place and recording to another.

    One deliberate exception: while a recording or replay is running, the slot stops following. Whatever slot the take started on stays its target until it ends, so that moving the picker mid-take cannot silently retarget Stop onto a different slot and overwrite the recording sitting there. The OSD picker will still move while you turn it — it just is not the live target until the take finishes.

  • Play Replay — plays that slot. The PAK restarts and the recording runs hands-free (press any button to take over). If the slot is empty it says so and leaves your game alone, rather than restarting the PAK to tell you.

Record and Stop deliberately stay in the pause menu: Record restarts the PAK, which is not something to put one OSD click away.

Also from the OSD, for a recording that isn't in one of your slots:

  1. Load PAK — pick the PAK you want.
  2. Load Replay — pick a .inp file directly. This is mainly for a recording someone sent you, since your own eight are already one button-press away. You can re-load the same recording as many times as you like.

Playback is bit-for-bit accurate: recording and replay both start from a PAK restart, the random-number seed is captured and restored, and the engine's per-frame timing is locked so the run reproduces identically regardless of load.

FPGA Technical Details

Both builds share the same FPGA core, identical timing.

  • Resolution: 320×224 active, 420×262 total (exact Sega CD NTSC H40+V28)
  • Refresh: 59.92 Hz (exact Sega CD NTSC)
  • Pixel clock: 53.693 MHz CLK_VIDEO / 8 = 6.712 MHz (exact Sega CD NTSC, colorburst-derived)
  • Pixel format: RGB565 (16 bits per pixel)
  • Audio: 48 kHz stereo S16 PCM via DDR3 ring buffer → I2S/SPDIF/DAC
  • Triple-buffered video via DDR3

Build Notes

OpenBOR_4086 — OpenBOR v3.0 Build 4086 from DCurrent/openbor (commit af23dc9c). Cross-compiled for MiSTer's ARM Cortex-A9 with SDL 1.2.15 and static linking. Video output goes through a patched SDL dummy driver that writes RGB565 directly to DDR3 for the FPGA to read.

OpenBOR_7533 — OpenBOR 4.0 Build 7533 from DCurrent/openbor (tag v7533). Cross-compiled for the same ARM Cortex-A9 with SDL 2.0.8 (pinned per upstream) and static linking. Video output goes through a patched SDL2 dummy framebuffer driver that writes RGB565 directly to DDR3. The patcher applies eleven targeted source modifications to v7533 (path redirects, R/B blend fix, missing script API names — cheats, PLAYER_MIN_Z, dot-as-damage_on_landing alias, etc.) so the engine boots cleanly into the dummy-driver pipeline.

Credits

  • SumolX — Created the first OpenBOR port for MiSTer
  • OpenBOR Team — Senile Team, ChronoCrash community, DCurrent, Plombo, Utunnels, White Dragon. Visit chronocrash.com
  • Sorgelig & MiSTer Community — MiSTer FPGA framework

License

GPL-3.0. See LICENSE. OpenBOR itself is BSD-3-Clause.

About

No description, website, or topics provided.

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages