Shell
The engine itself — a container that runs your built game.
What the Shell is
The Shell is the heart of Roves — the actual compiled engine binary. Everything else on this wiki (Packmaster, the GitHub Action) doesn't replace it — both just build or download this exact same Shell on your behalf and hand you the result already put together.
As the name suggests, the Shell is a container: an empty, pre-built native application
that knows how to load and run a dist — your game's already-built web output, whatever
your own bundler (Vite, webpack, or anything else that produces a browser-runnable
index.html) produces — as a real, double-click desktop app. No browser chrome, no toolbar,
no address bar — one window that opens your game and nothing else. It's the same role a
Tauri or Electron shell plays, just for a different underlying engine; see
Why Roves? for that comparison.
The Shell is already built for you
You don't need to compile anything to get a working Shell. Every tagged engine version
publishes a ready-to-run Shell — with no game content in it yet — on the
Releases page, starting at
v0.1.0. Each platform is published twice: a plain build and a _steam-suffixed one
compiled with the Steam bridge
built in.
| Platform | Download | Contains |
|---|---|---|
| Windows | roves_shell_windows.zip / _steam.zip | play.exe + the DLLs (incl. GStreamer's) it needs |
| macOS | roves_shell_macos.zip / _steam.zip | play.app, whose Contents/MacOS/play is the engine binary |
| Linux | roves_shell_linux.zip / _steam.zip | play (expects GStreamer already installed via your distro) |
These are optimized (--release) builds with the real GStreamer media stack (bundled
automatically on Windows/macOS). Pick the version your game targets, download the zip for
your platform, and extract it anywhere — that's the whole install step.
Inserting your dist into the Shell
This is the part that actually matters: turning that empty container plus your built game into something playable. Every path below — Packmaster included — ends up doing the exact same thing under the hood, so pick whichever fits how you work.
Nothing here requires Rust, Python, or mach — just a downloaded Shell and your already-built
game.
-
Build your game with your own bundler, however you already do it. This produces a folder — commonly named
dist/— containing anindex.htmlplus whatever assets it references. -
Download and extract the matching Shell zip from the table above.
-
Copy your build output next to the Shell binary (
play.exe/play.app/play), keeping it nameddistso it lines up with the Shell's own default expectations. -
Tell the Shell where to find it. Two ways, depending on whether you want a double-click-runnable copy or you're just testing:
-
A
launch.jsonnext to the binary (recommended — this is exactly whatmach bundle/Packmaster generate for you):launch.json { "url": "dist/index.html" }With this file in place, double-clicking the Shell binary opens your game directly — no arguments needed. This is what actually makes the container double-click-runnable.
-
Pass the path as an argument, for a quick check without creating any file:
from a terminal, or a shortcut's target ./play dist/index.htmlAny argument on the command line skips
launch.jsonentirely, so use this for testing, not for what you hand to a player.
-
No launch.json and no argument? The Shell isn't broken
Without either, the container has nothing to run — it just opens the stock Servo homepage. That's expected: it means no dist has been pointed at it yet, not that something failed.
This covers loose files only
Hand-writing launch.json this way ships your dist as plain, individually browsable files
(equivalent to mach bundle --content-compress=none). The compressed tar+zstd packing
described in Content packing
is produced by roves-content-packer, invoked either through mach bundle or by Packmaster
linking that same crate as a library — there's no manual equivalent for that path.
launch.json also accepts window_size and extra args — see the flags table further down
for what each corresponds to; a hand-written file only needs url to work.
Building the Shell yourself
This is the least important section for most people. You only need this if you're working on Roves itself, need a flag Packmaster doesn't expose, or want to understand what's actually happening underneath the paths above.
Roves' Shell is not a from-scratch engine — it's a vendored, customized fork of
Servo, built with Servo's own mach tooling. Every customization on top
of stock Servo (no browser chrome, the single-executable bundle, the boot splash, the
roves: protocol bridge, optional Steam) is tracked, file by file, in
CUSTOMIZATIONS.md.
git clone https://github.com/DRincs-Productions/roves
cd rovesBuild and bundle
./mach bootstrap
./mach build --releaseAdd --features steam if you need the Steam
bridge compiled in.
./mach bundle --content-dir path/to/dist --output releasemach bundle is what turns that build plus your dist into the container described above,
producing exactly the same launch.json-driven layout — no separate launcher process, no
roves-content-packer binary shipped alongside it (packed-content extraction happens
in-process, inside the engine binary itself, which already links that crate as a library):
| Platform | Portable output |
|---|---|
| Windows | play.exe next to a handful of DLLs it needs directly, with GStreamer's plugin DLLs (and their own private codec dependencies) tucked into a lib/ subfolder |
| macOS | play.app, whose Contents/MacOS/play is the engine binary |
| Linux | play |
Flags
| Flag | Default | Description |
|---|---|---|
--content-dir <dir> | — | Web content to copy/pack into the bundle. |
--html-file <path> | dist/index.html | Entry HTML file, relative to the bundle root. |
--output, -o <dir> | <build dir>/bundle | Where to write the bundle. |
--window-size <WxH> | 1280x720 | Initial window size in logical pixels. |
--content-compress | auto | auto: pack into a handful of tar+zstd archives (see Content packing). none: copy in as loose, individually browsable files. |
--content-compression-level | 1 | zstd level for --content-compress=auto. Low by default — speed over ratio. |
--content-max-pack-size | 500M | Split archives so none exceeds this size. |
--content-exclude <glob> | — | Repeatable. Leave matching files as loose, uncompressed files (e.g. a save-data folder that shouldn't sit inside a read-only archive). |
--content-boot-include <glob> | — | Repeatable. Extra files eagerly extracted at boot, beyond the html file and whatever it directly references. |
--diagnostic-script | off | Ship a diagnose.bat/diagnose.sh next to the binary that launches the game from a console and prints its exit code plus roves.log inline. See Diagnostics. |
| (trailing args) | — | Anything else is forwarded verbatim to the running game as CLI args (written into launch.json). |
Distributing installers
Each platform also has one installable alternative to the portable output above, wrapping the exact same staged files — nothing extra is left behind once the installer is built:
| Platform | Installer flag | Produces |
|---|---|---|
| Windows | --msi | An installable .msi |
| macOS | --dmg | play.app wrapped in an installable .dmg |
| Linux | --deb | An installable .deb |
| Flag | Default | Description |
|---|---|---|
--package-name <name> | roves | Package name for --deb/--msi/--dmg. |
--package-version <ver> | 0.0.0 | Package version for --deb/--msi/--dmg. |
Extra args after --output are forwarded to the game, not to mach
mach bundle's own flags (--package-name, --package-version, --msi, ...) are for
naming/shaping the installer. Anything else on the command line is written into the
bundle's launch.json and handed to the running game at launch as real CLI args — the two
are validated against each other so one can't accidentally leak into the other.