Games on the M5Stack Tab5, an
ESP32-P4 handheld with a 5" 1280x720 display, built with
ESPP. A launcher, a pause menu with save
states, and a Cart per game or emulator core, in the same shape as
esp-box-emu so glue can move
between the two.
Two of the carts are not emulators. They are native ports of PC and PlayStation games, running the games' own C code on the two 360 MHz RISC-V cores with a software rasterizer.
tab5-mgs.mp4
The FoxdieTeam decompilation of the PlayStation game
(mgs_reversing) compiled for
the ESP32-P4, with psyz standing in for
the PSY-Q SDK, the GPU, the GTE and the SPU. Both on esp32p4 branches of
esp-cpp forks. It boots the retail chain from the extracted US disc on the
card: the title, the opening, the dock, the elevator, topside; cutscenes
with the frame-buffer effects, codec calls, sound effects, music and
speech. Gameplay runs at about 28 fps against the game's own 30 fps cap,
drawn by two rasterizer workers, one per core, each taking a band of rows.
Save and load states from the pause menu. See
components/mgs/README.md.
tab5-jk.mp4
The 1997 PC game through OpenJKDF2
(our fork: esp-cpp/OpenJKDF2,
branch esp32p4): the engine's software rasterizer draws the world at a
low internal resolution and the P4's 2D accelerator scales it to the panel,
with the HUD composited at full resolution. Playable on hardware with
sound, music, saves, and a USB keyboard and mouse or a gamepad. The port
plan and its history are in docs/PLAN.md.
| Core | Source | Status |
|---|---|---|
| Genesis / Mega Drive | gwenesis (shared with esp-box-emu) | full speed |
| SNES | snes9x2005 | full speed |
| Game Boy Advance | gpSP | close to full speed; heavy scenes drop a few frames |
tab5-gba.mp4
| SoC | ESP32-P4 (dual RISC-V @ 360 MHz, FPU, PPA 2D accelerator, 32 MB PSRAM) |
| Display | 5" 1280x720 MIPI-DSI (portrait-native, rotated to landscape) |
| Touch | GT911 capacitive |
| Audio | ES8388 codec + NS4150B amp, dual mics |
| Storage | microSD (SDMMC 4-bit) |
| Input | touch (virtual gamepad), BOOT button (pause menu), USB keyboard / mouse / gamepad on the USB-A port (hubs supported) |
| USB-C | serial console, or the SD card as a USB drive (settings > "USB drive") |
main/ app_main, Cart base class, per-game carts
components/
tab5-emu/ HAL (Tab5Emu): display / PPA video path, audio, SD, input
gui/ LVGL launcher (rom list + boxart + settings)
menu/ LVGL in-game pause menu (resume/reset/save/load/quit)
rom_info/ metadata.csv parsing
statistics/ frame timing
jk/ OpenJKDF2 port: engine submodule + ESP32 platform layer + glue
mgs/ Metal Gear Solid: mgs_reversing + psyz submodules + the Tab5 platform layer
gba/ genesis/ snes/ the emulator cores and their glue
docs/PLAN.md the Jedi Knight plan and history
/sdcard/
metadata.csv # rom_path, boxart_path, name
boxart/*.jpg # baseline JPEG, ~400x400
jk/ # your Jedi Knight install (copy from CD / GOG)
jk.cd # (any marker file; metadata points here)
episode/JK1.gob JK1CTF.gob JK1MP.gob
resource/Res1hi.gob Res2.gob jk_.cd
resource/video/*.SMK # optional, cutscenes
MUSIC/Track12.wav ... # optional, the soundtrack: run
# tools/convert_music.py <game dir> on the GOG / Steam
# MUSIC folder (IMA ADPCM; the .ogg originals also
# play, but Vorbis decoding costs ~75% of a core)
player/ # saves / settings (created)
mgs/ # Metal Gear Solid, extracted from your own US disc
STAGE.DIR RADIO.DAT FACE.DAT ZMOVIE.STR VOX.DAT DEMO.DAT BRF.DAT
# (components/mgs/mgs_reversing/port/extract_disc.py)
saves/ # save states and their screenshots
Example metadata.csv (one line per game: rom path, boxart, name; the
platform comes from the path: .md/.gen Genesis, .sfc/.smc SNES,
.gba Game Boy Advance, jk.cd Jedi Knight, STAGE.DIR Metal Gear Solid):
jk/jk.cd, boxart/jk.jpg, Star Wars: Jedi Knight - Dark Forces II
mgs/STAGE.DIR, boxart/mgs.jpg, Metal Gear Solid
genesis/sonic.md, boxart/sonic.jpg, Sonic the Hedgehog
snes/smw.sfc, boxart/smw.jpg, Super Mario World
gba/mk.gba, boxart/mk.jpg, Mario Kart: Super Circuit
No game data is included. A real gba_bios.bin next to a GBA ROM is used
when present; otherwise the open-source BIOS built into gpSP is used.
Requires ESP-IDF v6.x.
git clone --recurse-submodules=components/jk/OpenJKDF2 \
--recurse-submodules=components/mgs/mgs_reversing \
--recurse-submodules=components/mgs/psyz \
https://github.com/esp-cpp/tab5-emu
cd tab5-emu
idf.py set-target esp32p4
idf.py build flash monitor
(Do not recurse into the engine's own submodules; they are desktop-only dependencies.)
Touch (virtual gamepad):
- Left third: d-pad (relative to the zone center)
- Right third: A (lower right), B (lower left), X (upper right), Y (upper left)
- Bottom strip of the middle third: SELECT (left) / START (right)
- Top-right corner, or the BOOT button: pause menu
USB keyboard / mouse / gamepad on the USB-A port (directly or through a
hub; the host runs at full speed so hubs work). Devices are decoded from
their HID report descriptors (espp hid-rp report map), so any standard HID
gamepad works: face buttons by position (A = east, B = south, X = north,
Y = west), shoulders and triggers = L / R (the triggers are also reported on
their own as L2 / R2), select / start (also the Consumer "back" / "menu"
controls of mobile pads), d-pad / hat / left stick = d-pad, select+start or
the home button = pause menu. Known layouts: Xbox-style and DirectInput /
DualShock numbering, Backbone Pro quirks. Keyboards: arrows, Z/X/A/S, Enter,
Shift/Backspace, Q/W (L/R) and E/R (L2/R2) map to the gamepad for the
emulators.
Jedi Knight gets the full keyboard and mouse look; on a gamepad the left stick strafes, the right stick looks, R = fire, L = secondary fire, A = activate, B = jump, X = crouch, Y = use item.
Metal Gear Solid uses the PlayStation layout by position: face buttons as Cross / Circle / Square / Triangle, L1 / R1 on the shoulders, L2 / R2 on the triggers for the item and weapon menus, so a pad with triggers (or the keyboard's E / R) is needed for those.
New controllers: the console prints each device's decoded buttons on change
(debug level in usb_hid.cpp); a wrong mapping is a quirk-table entry in
espp's hid-rp-report-map.hpp.
Core dumps go to flash (coredump partition) and are printed base64 on the
console at the next boot, so a crash while the console is unavailable (the
USB drive hand-over) can still be read. Decode with:
base64 -d < dump.b64 > core.bin
python -m esp_coredump --chip esp32p4 info_corefile -t raw -c core.bin build/tab5-emu.elf
Note that a terminal which opens the console port with DTR low and RTS high
resets the chip (rst:0x17 CHIP_USB_UART_RESET); keep both asserted, or
expect a reboot whenever the console re-enumerates (after the USB drive).
Settings > "USB drive" exposes the SD card as a USB mass storage device on the USB-C port, so ROMs, boxart and game data can be copied without pulling the card. While the drive is on the serial console is off (the Tab5's USB-C carries the P4's full-speed USB PHY, which the console and the USB device controller share) and the launcher cannot start games; switch it off (eject first) to get the card, the console and the ROM list back.
MIT (this project). OpenJKDF2, mgs_reversing and psyz are under their own licenses; the game data is not included and must come from your own copies of the games.







