kevinbatdorf / nativephp-retro-emulator
Native retro emulation for NativePHP Mobile apps — NES, SNES, Game Boy, Game Boy Color, Game Boy Advance, and Genesis via swappable cores (ares, SameBoy, mGBA, or bring-your-own libretro)
Package info
github.com/KevinBatdorf/nativephp-retro-emulator
Language:C++
Type:nativephp-plugin
pkg:composer/kevinbatdorf/nativephp-retro-emulator
Requires
- php: ^8.2
- illuminate/events: ^10.0|^11.0|^12.0|^13.0
- illuminate/queue: ^10.0|^11.0|^12.0|^13.0
- illuminate/view: ^10.0|^11.0|^12.0|^13.0
- nativephp/mobile: ^4.0
Requires (Dev)
- laravel/framework: ^13.23
- laravel/pint: ^1.29
- pestphp/pest: ^3.0
README
Native emulation for six consoles, NES through Genesis — zero setup.
See it running first: grab the demo APK and sideload it — all six systems, games included. Or build the demo app yourself in minutes.
Install
composer require kevinbatdorf/nativephp-retro-emulator php artisan native:plugin:register kevinbatdorf/nativephp-retro-emulator
Requires NativePHP Mobile v4. No BIOS files, no API keys, no setup — your first build for each platform downloads the prebuilt emulator cores automatically (checksum-verified, cached from then on).
The bundled engines (ares, SameBoy, mGBA — all permissively licensed) play everything. Want a lighter or more accurate core? Fetch it once and your builds bundle it from then on:
php artisan retro-emulator:fetch-core snes9x
| System | Fast | Accurate |
|---|---|---|
| NES | fceumm † | mesen † |
| SNES | snes9x † | bsnes † |
| Game Boy / Color | SameBoy | ares |
| GBA | mGBA | ares |
| Genesis | picodrive † | genesis_plus_gx † |
† fetched, not bundled — GPL and non-commercial licences stay out of your app unless you opt in; read LICENSING.md before shipping one. Fetched cores are Android-only; iOS plays everything through the bundled engines. Any other libretro core loads the same way.
A console in two lines
<native:emulator name="main" system="sfc" :rom="$romPath" /> <native:dpad surface="main" class="w-36 h-36" />
The element boots when it mounts. The d-pad is optional on-screen touch controls — physical gamepads work with zero setup, and port 1 connects the system's default pad automatically.
Play with the running game
use KevinBatdorf\RetroEmulator\{Emulator, Device}; use KevinBatdorf\RetroEmulator\Buttons\SfcButton; $emu = Emulator::surface('main'); $emu->saveState(); // instant snapshot, with undo $emu->toggleRewind(); // play time backwards $emu->setSpeed(2.0); // or slow-mo at 0.5 $emu->setShader($crtPreset); // real CRT shaders (librashader) $emu->addCheat('7E0010:01'); // live RAM patches $emu->connectDevice(1, Device::Gamepad)->press(SfcButton::A); $emu->watchMemory(0x7EF340, 2); // react as the game writes RAM
That memory watch means your Livewire component updates the moment Link picks up a heart piece:
#[On(MemoryChanged::class)] public function onGameProgress(array $payload) { /* … */ }
Inertia + Vue/React apps skip PHP entirely — every native function has a named JavaScript export:
import Emulator, { LoadRom, StateSave, onNativeEvent } from './vendor/kevinbatdorf/nativephp-retro-emulator/resources/js/index.js'; await Emulator.Boot({ system: 'snes' }); await LoadRom({ path: romPath }); onNativeEvent('EmulatorStarted', () => console.log('first frame'));
What's in the box
- Six systems, playable out of the box, verified on real hardware.
- Save states with undo, battery saves, instant rewind, fast-forward, run-ahead.
- Controllers: touch d-pad element, physical gamepads, 4-player multitap, mouse, rumble.
- Live memory: read, write, and watch RAM — build trackers, trainers, and game-reactive UI.
- Presentation: CRT shaders, color and gamma controls, aspect correction, screenshots.
- Cheats, region control, accuracy toggles, an OS ROM picker (
pickRom()), and window/safe-area metrics for overlay layouts.
Events
EmulatorStarted, EmulatorStopped, EmulatorPaused, EmulatorResumed, MemoryRead, MemoryChanged, EmulatorError, RomPicked, and WindowMetricsChanged — listen with #[On(...)] in Livewire or onNativeEvent in JS. All under KevinBatdorf\RetroEmulator\Events\.
Permissions and secrets
Android asks for VIBRATE (controller rumble); iOS asks for nothing. The shipped app makes no network, storage, camera, or location access, and the plugin needs no API keys or environment variables.
The deep end
The full reference — every method and config key, element props, engine resolution rules, bring-your-own libretro cores, memory windows, build internals — lives in AGENTS.md. Licence obligations live in LICENSING.md and THIRD-PARTY-NOTICES.md.
AI Disclosure
This project was built by the developer using AI tooling and autonomous coding agents. Design, architecture, and product decisions are human; implementation was AI-assisted under direction, with every change reviewed and verified on real hardware before shipping.
However, AI wrote the above too, so use your own judgement.