Roves

Shell

The engine itself — a container that runs your built game.

Edit on GitHub

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.

PlatformDownloadContains
Windowsroves_shell_windows.zip / _steam.zipplay.exe + the DLLs (incl. GStreamer's) it needs
macOSroves_shell_macos.zip / _steam.zipplay.app, whose Contents/MacOS/play is the engine binary
Linuxroves_shell_linux.zip / _steam.zipplay (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.

  1. Build your game with your own bundler, however you already do it. This produces a folder — commonly named dist/ — containing an index.html plus whatever assets it references.

  2. Download and extract the matching Shell zip from the table above.

  3. Copy your build output next to the Shell binary (play.exe / play.app / play), keeping it named dist so it lines up with the Shell's own default expectations.

  4. 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.json next to the binary (recommended — this is exactly what mach 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.html

      Any argument on the command line skips launch.json entirely, 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 roves

Build and bundle

from the repo root
./mach bootstrap
./mach build --release

Add --features steam if you need the Steam bridge compiled in.

./mach bundle --content-dir path/to/dist --output release

mach 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):

PlatformPortable output
Windowsplay.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
macOSplay.app, whose Contents/MacOS/play is the engine binary
Linuxplay

Flags

FlagDefaultDescription
--content-dir <dir>Web content to copy/pack into the bundle.
--html-file <path>dist/index.htmlEntry HTML file, relative to the bundle root.
--output, -o <dir><build dir>/bundleWhere to write the bundle.
--window-size <WxH>1280x720Initial window size in logical pixels.
--content-compressautoauto: pack into a handful of tar+zstd archives (see Content packing). none: copy in as loose, individually browsable files.
--content-compression-level1zstd level for --content-compress=auto. Low by default — speed over ratio.
--content-max-pack-size500MSplit 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-scriptoffShip 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:

PlatformInstaller flagProduces
Windows--msiAn installable .msi
macOS--dmgplay.app wrapped in an installable .dmg
Linux--debAn installable .deb
FlagDefaultDescription
--package-name <name>rovesPackage name for --deb/--msi/--dmg.
--package-version <ver>0.0.0Package 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.

On this page