Skip to content

Running on gasm ​

Built for gasmBuilt for gasm

Besides the macOS app, OpenRF builds as openrf.wasm, a game module for gasm, a portable game runtime on WebAssembly. The same file runs in gasm's native runner (gasm-run, macOS, Linux, Windows) and in its browser runner. It is the same engine as the app, with a different platform layer: the frames, sounds and gameplay are identical (the test runs below compare them pixel for pixel).

On gasm the game runs at a fixed 62.5 frames per second, one tick of the original's 16 ms game clock per frame, and everything is deterministic: the same inputs give the same game on every runner, which is what future online two-player play builds on.

Ready-made bundles ​

The easiest way to run the gasm build is a bundle from the releases: openrf.wasm, the released gasm-run it was tested with, and a launcher that asks for your CD once and remembers it. See Installing for the first start on each system.

BundleStart withSaved CD
openrf-gasm-<version>-macos-universal.zipReturn Fire (gasm).app (hold Option to change the CD)~/Library/Application Support/OpenRF/cd-location
openrf-gasm-<version>-linux-x86_64.tar.gz, -linux-arm64.tar.gz./openrf.sh [CD] (--install-desktop for a menu entry)~/.config/openrf/cd-location
openrf-gasm-<version>-windows-x86_64.zipOpenRF.cmd [CD]%APPDATA%\OpenRF\cd-location

The CD can be a folder (the disc, a mounted image, a copy; passed as --asset-dir) or a raw .bin or .iso file (--asset cd=); a .cue is refused, choose the .bin next to it. All three launchers take the same options: --change-cd, --forget-cd, --help, --dry-run (print the gasm-run command instead of running it), and pass anything after the CD on to gasm-run, for example --param level=12 --param play=1 or --mute. OPENRF_CD=<CD> uses a CD for one run without saving it. Each bundle has a README.txt with the same details, and the licences (OpenRF GPL-3.0, gasm-run MIT).

Get the files ​

To use your own gasm-run instead of a bundle:

  • openrf-<version>.wasm from the releases (with its SHA-256), or build it (below).
  • gasm-run 0.5.0 or newer from the gasm releases, or cargo install gasm-host. The module reads the raw keyboard, which gasm added in 0.5.0; older runners stop it with an error on the first frame.
  • Your Return Fire CD: the disc in a drive, a mounted disc image, or a folder you copied it to (see Game data). A raw .bin or an .iso also works without mounting.

Or skip the download: play in the browser, which runs the same module.

Run ​

Recommended: the CD's files. Point --asset-dir at the folder that holds RFIRE.BIN, ART, SOUND, TITLE and WORLDS: the CD itself, a mounted image (mount it) or an extracted copy. Names are matched case-insensitively, so any copy works.

sh
gasm-run openrf.wasm --asset-dir /Volumes/RFIRE        # macOS: the CD or a mounted .iso
gasm-run openrf.wasm --asset-dir D:\                   # Windows: the CD drive
gasm-run openrf.wasm --asset-dir /media/$USER/RFIRE    # Linux
gasm-run openrf.wasm --asset-dir ~/ReturnFire/cd       # an extracted folder

A disc image file: pass it as the asset named cd. A raw .bin (MODE1/2352) or an .iso works; for a .bin/.cue pair, pass the .bin (the cue sheet only names it).

sh
gasm-run openrf.wasm --asset "cd=Return Fire (Europe) (En,Fr,De,Es,It).bin"

Either way the data is read on demand, not loaded: the 220 MB music file is streamed and the game starts at once. Measured with gasm-run --headless (the fire demo below, 689 frames): 53 MB maximum resident memory with --asset-dir, 52 MB with --asset cd=<.bin>. Esc closes the runner. --mute silences it.

Mount your CD ​

A physical CD mounts by itself. A disc image has to be mounted to use it as a folder (or pass it with --asset cd= instead):

System.isoWhere it appears
macOSDouble-click it (DiskImageMounter)/Volumes/RFIRE
Windows 10/11Right-click, Mount (or double-click)a new drive letter, e.g. E:\
Linuxsudo mount -o loop,ro rf.iso /mnt/rfire, or GNOME Disks: Attach Disk Image/mnt/rfire, or /media/$USER/RFIRE

A raw .bin/.cue pair (MODE1/2352) can't be mounted by these tools: convert it to an .iso first (Extract the CD image), or give the .bin to OpenRF directly. More in Mount it.

Launch parameters ​

Parameters replace the app's command-line options and environment variables. Pass them as --param name=value (in the browser runner: URL query parameters).

ParameterEffectApp equivalent
skip_intro=1Skip the intro stills and movies--skip-intro
play=1Go straight into the level map (level 1 by default)--play
play2=1Go straight into a two-player game ("Driving School", or the level 2-player map)--play2
level=<n or path>Map: 1-100 one-player, 101-204 two-player, or a path such as WORLDS/2PLAYER/LEVEL3/RFMAP115.RFM--level
viewer=1The map viewer--viewer
p1=<name>, p2=<name>Player names for the high scores (default "Player 1" / "Player 2")$USER, OPENRF_P1/P2
demo=<mode>Scripted input for tests: 1, fire, jeep, msv, heli, turret, drone, sub, rules, win; with play2=1: 2p, 2pheli, 2pwin, 2pspectateOPENRF_DEMO
cam_h=<n>Driving camera height (debug)OPENRF_CAM_H
sfx_log=1Sound-effect log (debug)OPENRF_SFX_LOG
sh
gasm-run openrf.wasm --asset-dir cd --param skip_intro=1 --param level=12 --param play=1

The title screen's number keys (1-9, Shift+1-9) work as in the app; level= picks any map.

Controls ​

The game reads the keyboard itself, with the original key bindings, exactly as in the macOS app: W A S D and H J K Q E for player 1, the keypad for player 2, F2 / F3, 1-9, Alt + 3, Esc (see Controls). gasm's keyboard layout (--keymap, keymap.txt) doesn't apply to OpenRF: the module switches it off (input_mode KEYS_RAW), so no key reaches the game twice.

Gamepads still arrive as gasm's virtual pads, the first connected for player 1 and the second for player 2, mapped as in Controls.

Esc: a tap leaves the level (as in the original); holding it for a second quits gasm-run (in the browser: stops the game). Alt + Enter and M (fullscreen, mute in the app) are the runner's business on gasm.

In the browser ​

Play runs openrf.wasm in gasm's browser host, in a Web Worker. You choose your CD folder once (Chrome and Edge: a folder picker; Firefox and Safari: a folder upload dialog); the page checks it (RFIRE.BIN, ART/ART.CAR, SOUND/SCORE.WAV) and copies the parts the game uses (about 275 MB: RFIRE.BIN, ART, SOUND, TITLE, WORLDS) into the site's private browser storage (the origin private file system, OPFS). The game then reads it on demand, and later visits start at once. Play without importing reads the picked files in place instead; a disc image (.iso or raw .bin) works as well. Nothing is uploaded: the files stay on your computer.

  • Sound starts with the first click (browsers require it). Fullscreen: the button, or double-click the picture.
  • High scores are kept in the site's IndexedDB. Remove imported data deletes the CD copy (not the scores).
  • The browser may clear site data when the disk is nearly full; Keep it asks it not to (navigator.storage.persist()).
  • Test parameters (URL query): the launch parameters below, and hashframes=N to run N frames without input and print the video_fnv32=... line of gasm-run --headless.

High scores ​

High scores are kept in gasm's per-game storage under the key RFire_HS, in the same format as the app's file. The namespace is the module's file name, so for openrf.wasm:

  • gasm-run: ~/Library/Application Support/gasm/openrf/RFire_HS (macOS), ~/.local/share/gasm/openrf/RFire_HS (Linux), %APPDATA%\gasm\openrf\RFire_HS (Windows); --storage-dir <dir> puts them elsewhere. A renamed module (openrf-0.2.0.wasm) gets its own namespace; --storage-id openrf keeps the old one.
  • Browser: the site's IndexedDB (database gasm, namespace openrf); on this site's play page that is openrf.emdzej.pl's storage.
  • Headless runs start with empty storage (unless --storage-dir is given).

To carry over the app's scores, copy ~/Library/Application Support/Return Fire/RFire_HS into the gasm directory.

Build ​

Needs CMake, wasi-sdk and gasm's C SDK (gasm-c-sdk-<version>.zip from the gasm releases, or the sdk/c folder of a gasm checkout). The C SDK (gasm.h, the toolchain file) must be 0.5.0 or newer (the raw keyboard imports), and so must the runner. tools/fetch-gasm-sdk.sh downloads both into .deps/:

sh
tools/fetch-gasm-sdk.sh
cmake -S . -B build-gasm -DOPENRF_PLATFORM=gasm -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_TOOLCHAIN_FILE=.deps/gasm-c-sdk/cmake/gasm-toolchain.cmake -DWASI_SDK_PREFIX="$PWD/.deps/wasi-sdk"
cmake --build build-gasm -j        # -> build-gasm/openrf.wasm (about 300 KB)

OPENRF_GASM_OPT picks the optimisation level (default -O2; -Oz is about 50 KB smaller and 20% slower). To skip the start-up compilation (about 45 ms), compile it ahead of time: gasm-run openrf.wasm --compile openrf.cwasm, then run the .cwasm with the same options.

Headless runs and checks ​

gasm-run --headless N runs N frames without a window or sound and prints hashes of everything the game showed and played; with --screenshot out.png it saves the last frame. Frame N shows the game at 16 x (N - 1) ms, the frame the app saves with OPENRF_FIXED_STEP=1 OPENRF_SHOT_MS=16 x (N - 1).

sh
gasm-run openrf.wasm --asset-dir cd --headless 689 --screenshot fire.png \
  --param skip_intro=1 --param play=1 --param demo=fire
# frames=689 presented=689 size=640x480
# video_fnv32=d014dcfb audio_fnv32=f1520dc1 audio_frames=486158

--input "FROM-TO:BUTTON+BUTTON,..." scripts pad 1 by frame number, for example start a game, launch the tank, drive and fire:

sh
gasm-run openrf.wasm --asset-dir cd --headless 780 --screenshot drive.png --param skip_intro=1 \
  --input "100-104:START,250-254:A,450-760:UP,600-640:LEFT,740-744:A,770-774:A"

Keys work the same way with KEY(...) (W3C key names); this run gives the same hashes as the pad script with START, UP + A and LEFT:

sh
gasm-run openrf.wasm --asset-dir cd --headless 900 --param skip_intro=1 \
  --input "30-35:KEY(F2),300-700:KEY(KeyW+KeyH),720-760:KEY(KeyA)"

gasm's headless Node runner (node runners/web/headless.mjs, same options) prints the same hashes, and so does the browser player (/play/?hashframes=689&skip_intro=1&play=1&demo=fire, after importing the CD): the same module, CD and parameters give the same frames and sound on every runner, whether the data comes from an image, a folder, OPFS or picked files. Two players without input: --param play2=1 --param demo=2p (or 2pheli, 2pwin, 2pspectate) scripts both pads (900 frames: video_fnv32=660f4ab4 audio_fnv32=31f91835).

Speed (Apple M-series, gasm-run --headless --no-hash, two-player split screen): about 1750 frames per second, 28 times real time; hashing every frame's pixels brings it to about 450.

OpenRF is released under the GPL-3.0. Return Fire is © 1995–1996 Silent Software / Prolific. OpenRF contains no original code or assets. The cross-platform and browser builds run on gasm.