Romulus is a local-first emulator library manager that scans, organises, enriches, and launches classic game libraries through a unified backend and frontend architecture.
It is designed to sit above individual emulators, providing consistent metadata, library management, and launch control — with Libretro used as the primary abstraction layer for emulator cores.
Emulation ecosystems tend to evolve organically rather than deliberately.
Most existing frontends grow around a specific emulator, operating system, or media-centre philosophy. Over time this leads to tight coupling, implicit behaviour, and UI logic that quietly encodes emulator-specific assumptions.
Romulus exists to explore a different approach:
- Treat emulators as replaceable execution engines
- Treat libraries and metadata as first-class domain concepts
- Keep orchestration explicit, observable, and local-first
- Prefer stable abstractions over convenience shortcuts
By sitting above individual emulators and delegating execution through Libretro, Romulus aims to remain understandable, extensible, and boring in the right ways — even as libraries and systems scale.
This repository is structured to reflect separation of concerns rather than feature groupings.
Reviewers are encouraged to start here:
-
src/Romulus.App
Core ASP.NET API, domain logic, feature flags, and orchestration -
src/Romulus.LibretroHost
Isolated execution host responsible for launching Libretro cores -
src/Romulus.Frontend
JavaScript SPA compiled and served by the backend
The most important design decisions live at the boundaries between these components, particularly around process isolation, configuration, and launch control.
- Frontend: JavaScript SPA, built and served as static assets
- Backend: ASP.NET Core Web API
- Execution model: Local-first, no cloud dependency
- Emulation layer: Libretro cores launched via a dedicated host process
The backend serves the compiled frontend from wwwroot, acting as both API and application host.
- Build the frontend
cd src/Romulus.Frontend
npm install
npm run buildThe build output is emitted into the backend’s wwwroot directory.
- Run the backend
cd ../Romulus.App
dotnet runThe API will serve both the backend endpoints and the frontend UI.
Certain subsystems are deliberately gated behind feature flags.
The acquisition module is currently disabled by default and can be enabled in src/Romulus.App/appsettings.Development.json:
"App": {
"FeatureFlags": {
"Acquisition": true
}
}This allows experimental functionality to evolve without destabilising the core library workflow.
ROM directories are registered via:
POST /api/library/rootsTrigger a scan of all enabled roots with:
POST /api/library/scanScanning is non-destructive and designed to be repeatable as libraries evolve.
Romulus integrates with Libretro to provide emulator-agnostic core discovery and launching.
Discover available cores:
POST /api/libretro/discoverList known cores:
GET /api/libretro/coresLibretro paths are configured in appsettings.json:
"Libretro": {
"Enabled": true,
"CoreDirectory": "libretro\\cores",
"InfoDirectory": "libretro\\info",
"HostPath": "C:\\path\\to\\Romulus.LibretroHost.exe"
}Launch a game via Libretro using:
POST /api/libretro/launchPayload:
{
"corePath": "...",
"gamePath": "..."
}The Libretro host executable is built from:
src/Romulus.LibretroHost
HostPath should point to the compiled output.
This separation keeps emulator execution isolated from the main API process.
SDL runtime:
- Place
SDL3.dll(orSDL2.dll) next toRomulus.LibretroHost.exe, or insrc/Romulus.LibretroHost/runtimes/win-x64/native/.
Romulus supports importing metadata from local files, avoiding mandatory third-party services.
Path-based import:
POST /api/metadata/import-localMultipart file upload:
POST /api/metadata/import-local-uploadCSV Format
Required headers:
title,systemKey,description,releaseYear,genres,coverArtPath
Notes:
genres uses ; as a separator
coverArtPath is treated as a local relative path
JSON Example
[
{
"title": "Super Metroid",
"systemKey": "snes",
"description": "A classic action adventure.",
"releaseYear": 1994,
"genres": ["Action", "Adventure"],
"coverArtPath": "covers/super-metroid.jpg"
}
]-
Local-first, offline-capable by default
-
Emulator-agnostic via Libretro abstraction
-
Explicit configuration over implicit behaviour
-
Extensible metadata and system modelling
-
Clear separation between UI, API, and execution layers
Romulus is intentionally structured to grow without collapsing into emulator-specific conditionals or hard-coded assumptions.
┌──────────────────────┐
│ Frontend │
│ (JS SPA / UI Layer) │
└─────────▲────────────┘
│ HTTP (API)
│
┌─────────┴────────────┐
│ Romulus.App │
│ ASP.NET Core API │
│ │
│ - Library scanning │
│ - Metadata handling │
│ - Core orchestration │
│ - Feature flags │
└─────────▲────────────┘
│ Process launch
│ (explicit boundary)
┌─────────┴────────────┐
│ Romulus.LibretroHost │
│ (Execution Host) │
│ │
│ - Core invocation │
│ - Argument mapping │
│ - Runtime isolation │
└─────────▲────────────┘
│ Libretro API
│
┌─────────┴────────────┐
│ Libretro Cores │
│ (Emulation engines) │
└──────────────────────┘This structure deliberately separates UI, coordination, and execution so emulator runtime behaviour cannot destabilise the application or API process.
-
Romulus is intentionally opinionated about what it does not attempt to solve.
-
Romulus does not bundle ROMs, BIOS files, or emulator cores
-
Romulus does not provide piracy tooling or download copyrighted content
-
Romulus does not attempt to replace emulators or re-implement emulation logic
-
Romulus does not require cloud services, accounts, or always-online behaviour
-
Romulus does not aim to be a “media centre” or game store clone
The project focuses strictly on organisation, metadata, and launch orchestration for libraries the user already owns.
Short-term goals are scoped to stability and clarity rather than feature volume.
-
Harden Libretro core discovery and validation
-
Improve scan performance and incremental re-scanning
-
Frontend UX refinements for large libraries
-
Better diagnostics and structured logging around launches
-
Optional metadata provider adapters (opt-in, non-mandatory)
-
Per-system launch profiles and overrides
-
Save-state and runtime configuration awareness
-
Improved test coverage across API boundaries
-
Netplay, achievements, or social features
-
Cross-device sync
-
Mobile clients
-
Emulator-specific UI branching
Growth is intentional and additive — Romulus should remain understandable even as it becomes more capable.
This project is part of a broader set of systems exploring the same principles at different scales:
- Clear domain boundaries
- Explicit behaviour over implicit magic
- Separation between orchestration and execution
- Extensibility without conditional sprawl
- Systems that remain understandable as they grow
The goal across all projects is not feature count, but credibility, correctness, and maintainability.
- AnyCalc — demonstrates deterministic logic and modular design at small scale
- TaskFlow — demonstrates auth, roles, and data modelling at application scale