Game Boy / Game Boy Color emulator running as native x86_64 shellcode on PS5,
delivered through Luac0re. No kernel
exploit — everything runs in userland inside the ps2emu sandbox of
Star Wars Racer Revenge.
Credits
- Gezine — Luac0re, the JIT exploit
and Lua delivery framework this runs on. No Luac0re, no project. - egycnq — the savedata write method, which
is what makes in-game saves persist, and EmuC0re,
whose PS5 runtime structure this follows. - FIX94 — fixGB, the emulation core (MIT).
Install
Download gb.lua below. You need Luac0re already working, with its loader
listening on port 9026.
1. Put your ROMs in the savedata
ROMs are read from a roms/ folder inside the game's savedata container —
the same container Luac0re lives in — and nowhere else.
Using any PS5 save manager that can mount and write a savedata image:
/savedata0/
lua/ <- Luac0re (already there, leave it alone)
roms/ <- CREATE THIS. your .gb / .gbc / .gbs files
saves/ <- CREATE THIS, empty. cartridge saves land here
VMC0.card <- leave alone
VMC1.card <- leave alone
sce_sys/ <- leave alone
Mount the save, create roms/ and saves/, copy your ROMs into roms/, then
unmount to commit.
Do this with the game closed. Mounting the savedata image while the game is
running desynchronises the game's own mount — directories read back empty and
writes fail. Close the game, make changes, unmount, then launch.
Watch the space. The container is around 42 MB and the two PS2 virtual
memory cards take 16.5 MB of it before you start, leaving roughly 24 MB for
ROMs and saves.
Optional: a genuine 2304-byte GBC boot ROM named exactly gbc_bios.bin in
roms/ enables the GBC boot animation and colourises DMG games. Anything
smaller is ignored. Not included.
2. Launch the game
Start Star Wars Racer Revenge, then OPTIONS → HALL OF FAME to arm the
Luac0re loader.
3. Send the payload
python gb_launcher.py <PS5_IP>
Or send gb.lua to port 9026 by any means you already use.
Controls
| DualSense | Game Boy |
|---|---|
| Cross | A |
| Square | B |
| Triangle | Select |
| Circle / Options | Start |
| D-Pad | D-Pad |
| L1 | back to the ROM picker (saves first) |
| R1 (hold 1 second) | quit |
R1 requires a full second held deliberately: quitting ends the Luac0re session
and means relaunching the game, so a brushed shoulder button shouldn't do it.
A web controller is also served at http://<PS5_IP>:9030 — touch, keyboard and
Gamepad API. The input source locks to whichever device you press first.
Saving
Cartridge saves are written into saves/ inside the savedata container, so a
backup of the save captures your games too. Press L1 to return to the
picker — that commits the save. Yanking power or killing the process will not.
Changed in this build
The FTP server that used to listen on port 1337 has been removed.
It could not have worked as shipped: uploads were written into the savedata
container, which is mounted read-only for the game, so every transfer came
back 550 Cannot create file and the picker stayed empty. It also could not
remove a dependency, because installing Luac0re into that container already
requires a save manager -- and the same tool is what puts ROMs in.
Removing it drops the payload from 63008 to 56480 bytes, takes a wait off
every boot, and removes a failure mode from the launch path. gb_launcher.py
is now purely a payload sender, with an optional --log.
The empty-list screen also used to say ROMs go in CONTENT_TMP. That stopped
being true when ROM discovery became savedata-only; it now points at the
savedata roms/ folder.
Known limits
- One payload send per Luac0re session. The emulator takes over the display and
stops ps2emu's graphics thread, so after it exits the loader can't serve
another payload — relaunch the game to send again. - No save states, no rewind, no fast-forward.
.gbsmusic files load, but the player UI is minimal.
Supported
MBC1, MBC2, MBC3 (incl. RTC), MBC5, HuC1. .gb, .gbc, .gbs.
Files smaller than 32 KB are rejected as cartridges.