Files
RP412/pack-dist.ps1
T
CydandClaude Fable 5 abd720dd6e Ship the controls map as a page in the dist
The published artifact source now lives in docs/, and pack-dist emits it
as CONTROLS.html beside the plain-text CONTROLS.txt so players get the
diagrams without the repo.

The artifact source is a fragment - the publisher supplies the document
shell - so it is wrapped here with a doctype, charset and viewport.
Without those a browser opens it in quirks mode and renders the
typography as mojibake. Written without a BOM so the charset declaration
is the only thing speaking.

The map itself gains a section for the mouse: the button banks reach
under the glass, and the six displays are the players to size and the
radar to place. Verified the five radar-placement diagrams against the
layout they describe - box 16:9, radar 16.7x39.3% of it, centred flush
to the bottom edge, flush in either bottom corner, and 30/30 vertically
when halfway up a side.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 14:39:17 -05:00

408 lines
17 KiB
PowerShell

# ============================================================================
# pack-dist.ps1 - assemble a runnable Red Planet 4.12 package into dist\
# ============================================================================
#
# Collects everything the game needs at run time:
# - Release\rpl4opt.exe (+ .pdb for crash debugging)
# - game data from assets\RP411 (AUDIO, GAUGE, VIDEO, INIs, RPL4.RES,
# TEST.EGG) - but not the arcade launch scripts or the old 4.10 exe
# - libsndfile-1.dll beside the exe; OpenAL32.dll copied from the system
# when installed, with oalinst.exe included as the fallback installer
# - a desktop environ.ini (PAD;KEYBOARD controls, on-screen plasma)
# - start-windowed.bat and a README
#
# Usage: powershell -ExecutionPolicy Bypass -File pack-dist.ps1 [-Zip]
#
param(
[switch]$Zip
)
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $MyInvocation.MyCommand.Path
$dist = Join-Path $root 'dist'
$assets = Join-Path $root 'assets\RP411'
$exe = Join-Path $root 'Release\rpl4opt.exe'
if (-not (Test-Path $exe)) {
throw "Release\rpl4opt.exe not found - build first (see BUILD.md 2)."
}
# Refuse to touch a dist the game is currently running from.
$running = Get-Process rpl4opt -ErrorAction SilentlyContinue |
Where-Object { $_.Path -like "$dist\*" }
if ($running) {
throw "rpl4opt.exe is running from $dist (PID $($running.Id)) - close the game first."
}
Write-Host "Packing into $dist"
if (Test-Path $dist) { Remove-Item -Recurse -Force $dist }
New-Item -ItemType Directory -Force "$dist\SPOOLS" | Out-Null
# --- game binary -----------------------------------------------------------
Copy-Item $exe $dist
$pdb = Join-Path $root 'Release\rpl4opt.pdb'
if (Test-Path $pdb) { Copy-Item $pdb $dist }
# steam_api.dll: the exe imports it (RP412_STEAM build). The Steam wire
# only activates with RP412STEAM=1; plain desktop runs never touch it.
Copy-Item (Join-Path $root 'extern\steamworks_sdk_164\sdk\redistributable_bin\steam_api.dll') $dist
# steam_appid.txt: until RP412 has its own AppID, Steam testing runs
# under Spacewar (480). Without this file SteamAPI_Init fails and the
# game falls back to plain TCP - which is exactly the confusing symptom
# testers hit when the file goes missing. Delete it once we ship under
# our own AppID (the Steam client provides it then).
Set-Content -Path "$dist\steam_appid.txt" -Encoding ascii -Value '480'
# --- game data -------------------------------------------------------------
foreach ($dir in 'AUDIO', 'GAUGE', 'VIDEO') {
Write-Host " copying $dir..."
Copy-Item -Recurse (Join-Path $assets $dir) $dist
}
foreach ($file in 'RPDPL.INI', 'JOYSTICK.INI', 'RPL4.RES', 'TEST.EGG',
'libsndfile-1.dll', 'oalinst.exe') {
Copy-Item (Join-Path $assets $file) $dist
}
# The controls map travels with the game (players get the diagrams
# without needing the repo). Flattened to ASCII so it reads correctly
# in Notepad - the markdown source keeps its typography.
$controls = Get-Content (Join-Path $root 'docs\CONTROLS.md') -Raw -Encoding UTF8
foreach ($pair in @(
@([char]0x2014, '-'), @([char]0x2013, '-'), @([char]0x2018, "'"),
@([char]0x2019, "'"), @([char]0x201C, '"'), @([char]0x201D, '"'),
@([char]0x00D7, 'x'), @([char]0x2192, '->'), @([char]0x2026, '...'))) {
$controls = $controls.Replace([string]$pair[0], [string]$pair[1])
}
Set-Content -Path "$dist\CONTROLS.txt" -Encoding ascii -Value $controls
# The same map as a page, for anyone who would rather look at the
# diagrams than read them. Its source is the published artifact, which is
# a fragment - the publisher supplies the document shell - so wrap it to
# stand alone: without a doctype the browser drops into quirks mode, and
# without a charset the typography arrives as mojibake. Written without a
# BOM so the charset declaration is the only thing speaking.
$controlsPage = Get-Content (Join-Path $root 'docs\rp412-controls.html') -Raw -Encoding UTF8
$page = @"
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
$controlsPage
</body>
</html>
"@
[System.IO.File]::WriteAllText(
"$dist\CONTROLS.html", $page, (New-Object System.Text.UTF8Encoding $false))
# --- OpenAL runtime --------------------------------------------------------
# The exe links OpenAL32.dll (32-bit). Prefer shipping the already-installed
# runtime beside the exe; oalinst.exe covers machines where that misses.
$openal = "$env:WINDIR\SysWOW64\OpenAL32.dll"
if (-not (Test-Path $openal)) { $openal = "$env:WINDIR\System32\OpenAL32.dll" }
if (Test-Path $openal) {
Copy-Item $openal $dist
$wrap = Join-Path (Split-Path $openal) 'wrap_oal.dll'
if (Test-Path $wrap) { Copy-Item $wrap $dist }
Write-Host " OpenAL runtime copied from $(Split-Path $openal)"
} else {
Write-Warning "OpenAL32.dll not found on this system - dist relies on oalinst.exe"
}
# --- desktop configuration -------------------------------------------------
Set-Content -Path "$dist\environ.ini" -Encoding ascii -Value @"
# ============================================================================
# environ.ini - Red Planet 4.12 configuration
# ============================================================================
# One KEY=VALUE per line, read at game start. Lines starting with # or ;
# are comments; anything without an = is ignored. Delete a line (or
# comment it out) to fall back to the built-in default.
#
# Input bindings live in bindings.txt beside the exe (written with the
# full documented layout on first run; delete it to restore defaults).
# ---- Core (the shipped configuration) --------------------------------------
# Control stack: tokens separated by ; or , processed left to right.
# PAD the virtual RIO (XInput controller + keyboard,
# rebindable via bindings.txt)
# RIO real serial cockpit hardware on COM1
# RIO:COMn same, on another port (RIO:COM3, ...)
# KEYBOARD the engine keyboard handler
# MOUSE, JOYSTICK, FLIGHTSTICKPRO, THRUSTMASTER, DIJOYSTICK
# legacy pointer/joystick drivers (untested here)
# Unset falls back to KEYBOARD alone.
L4CONTROLS=PAD;KEYBOARD
# Renderer bring-up argument. Only its presence is checked (the DPL
# resolution parsing it once fed is gone) and the game refuses to start
# without it - any non-empty value works. Leave as shipped.
DPLARG=1
# DPL (renderer/scene) configuration file, searched beside the exe.
# Any notation file name; RPDPL.INI is the one that ships.
L4DPLCFG=RPDPL.INI
# Gauge (MFD/instrument) canvas. Must name a page of GAUGE\L4GAUGE.INI:
# 640x480x8 | 640x480x16 | 800x600x16
# Unset disables the gauge renderer (and with it all MFDs).
L4GAUGE=640x480x16
# Plasma display.
# SCREEN render the pod's plasma glass in-window (currently
# parked off-layout)
# COM1, COM2... drive real plasma glass on that serial port
# (9600 baud, N81)
# Unset = no plasma display.
L4PLASMA=SCREEN
# 0 = classic separate gauge windows; 1 = the single-window glass
# cockpit (all seven displays composed on a locked 1920x1080 canvas
# around the viewscreen); 2 = exploded diagnostic view (each display
# in its own native-resolution desktop window - MFDs 640x480, map
# 480x640 - decoded exactly as the pod's VDB split them, no downscale).
L4MFDSPLIT=1
# Size of the six secondary displays in the glass cockpit, as a
# percentage of their pod size. The pod bolted them down at one size;
# on a big panel there is room to trade viewscreen for instrument, so
# turn these up if you want to actually read the other displays while
# you fly. 100 = as the pod had them. Range 25-200 (out-of-range and
# unreadable values fall back to the group setting, then to 100).
#
# The scaling is applied in canvas units, before the cockpit is fitted
# to your window, so a given number looks the same on every monitor.
# The layout stays legal whatever you ask for - the panes are clamped
# against their actual neighbours, shrinking uniformly so a display
# never comes out stretched. They do overlap the viewscreen, exactly
# as the pod's bezels did, but never each other.
#
# L4MFDSCALE sets all five green MFDs at once.
L4MFDSCALE=100
# ...and any single display can override it. Uncomment one to size it
# on its own - useful if you only care about, say, the damage readout.
# UL upper left UC upper center UR upper right
# LL lower left LR lower right
#L4MFDSCALE_UL=100
#L4MFDSCALE_UC=100
#L4MFDSCALE_UR=100
#L4MFDSCALE_LL=100
#L4MFDSCALE_LR=100
# The portrait radar/map, sized on its own (it already sits at 1.35x
# the MFDs by default). It shares the canvas with whichever MFD is
# above it, so at extreme settings one of the two gives way.
L4RADARSCALE=100
# Where the radar sits:
# CENTER bottom centre, under the viewscreen, as the pod had it
# (default; BOTTOM and CENTRE mean the same)
# LEFT bottom left corner (or BOTTOMLEFT)
# RIGHT bottom right corner (or BOTTOMRIGHT)
# MIDLEFT left edge, halfway up (or LEFTCENTER / LEFTCENTRE)
# MIDRIGHT right edge, halfway up (or RIGHTCENTER / RIGHTCENTRE)
# Anywhere but CENTER stops it blocking the middle of the road, which
# is worth having on a wide screen.
#
# In a bottom corner it is one of three panes along the bottom, and the
# lower MFD whose corner it takes slides inboard beside it. Halfway up
# a side it leaves the bottom row entirely and sits between that side's
# two MFDs - roomy on a tall radar, but if the MFDs on that side are
# also scaled up, the radar is the one that gives way (it has to clear
# both of them, and it grows from the middle in both directions).
L4RADARPOS=CENTER
# Simulation/render frame rate, integer frames/second. The desktop
# default is 60; the arcade pods shipped at 25.
TARGETFPS=60
# 1 = Steam networking (lobbies, FakeIP mesh). Needs the Steam client
# running and steam_appid.txt beside the exe; without them the game
# logs the reason and falls back to plain TCP. 0 = TCP only.
RP412STEAM=1
# ---- Optional ---------------------------------------------------------------
# RGB keyboard lamp mirror (Windows Dynamic Lighting): keys bound to
# lamp buttons glow with the panel, flash modes and all.
# Unset or nonzero = on (the default); 0 = off.
#RP412KEYLIGHT=0
# Invert the stick on top of whatever bindings.txt produces:
# X = invert X only, Y = invert Y only, XY = both (case-insensitive).
#L4PADFLIP=XY
# Anti-aliasing sample count, passed straight to Direct3D 9:
# 0 = off, else 2..16 as the GPU supports (1 selects the driver's
# "nonmaskable" mode; unsupported counts fail device creation).
#MULTISAMPLE=0
# Particle budget, integer. Default 8192.
#MAXPARTICLES=8192
# On-screen plasma glass (L4PLASMA=SCREEN only). SCALE = integer pixel
# size 1..16, default 4 (out-of-range values are ignored). POS = window
# top-left as X,Y screen coordinates; unset = auto, parked below the
# main window.
#L4PLASMASCALE=4
#L4PLASMAPOS=0,0
# Fixed random seed (repeatable runs): any unsigned integer.
# Unset seeds from the clock.
#RANDOM=12345
# ---- LAN play without Steam -------------------------------------------------
# Host a race over plain TCP: list the member pods' console channels
# (members run: rpl4opt.exe -windowed -res 1920 1080 -net 1501).
# RP412HOSTPODS comma-separated IP[:port] list, one entry per member
# pod; port defaults to 1501 per entry
# RP412HOSTPORT this machine's console port, integer > 0
# (default 1501)
# RP412HOSTADDR this machine's LAN IP as members can reach it
# (default 127.0.0.1)
#RP412HOSTPODS=192.168.1.20:1501,192.168.1.21:1501
#RP412HOSTPORT=1501
#RP412HOSTADDR=192.168.1.10
# ---- Developer / testing ----------------------------------------------------
# Nonzero arms the debug keys: Alt+W wireframe, Alt+V predator vision,
# Alt+F frame dump, Alt+/ perf stats, Alt+E event-queue dump.
# 0 or unset = off. (Alt+Q, the mission abort, is always live.)
#RP412DEVKEYS=1
# Console race-length override, integer seconds (short test races).
# Values <= 0 are ignored.
#L4CONSOLELEN=30
# Nonzero = Steam transport loopback self-test at boot (logs PASS/FAIL).
#RP412STEAMSELFTEST=1
# ---- Arcade heritage (multi-monitor pods; not used on the desktop) ----------
# PRIMGAUGE / SECGAUGE / MFDGAUGE / MFDGAUGE2 pin a display to a monitor
# by adapter index (0, 1, 2...). SPANDISABLE: 0 = let the MFDs span one
# wide surface, nonzero = separate windows (setting MFDGAUGE2 alone also
# forces spanning off). L4EYES = "x y z xrot yrot zrot [type]" floats
# for a detached camera; a type starting with r offsets it relative to
# the pod. L4INTERCOM enables the crew intercom - only its presence
# matters (traditionally COM2). NOMODES skips the mode/lamp programming;
# presence alone triggers it, even NOMODES=0. LOGSIZE > 0 sizes the
# trace log in dev builds compiled with tracing.
#PRIMGAUGE=1
#SECGAUGE=2
#MFDGAUGE=3
#MFDGAUGE2=4
#SPANDISABLE=1
#L4EYES=1
#L4INTERCOM=COM2
#NOMODES=1
#LOGSIZE=1000000
"@
Set-Content -Path "$dist\start-windowed.bat" -Encoding ascii -Value @"
@echo off
rem Red Planet 4.12 - desktop prototype. Boots into the game-setup
rem front end. The cockpit fits itself to the window and keeps 16:9,
rem so a wider screen letterboxes rather than stretching. -res sets the
rem 3D render size - raise it to match a big screen for sharper pixels,
rem e.g. -res 2560 1440.
rem (Add -egg TEST.EGG to skip the menu and run the canned mission.)
cd /d "%~dp0"
start rpl4opt.exe -windowed -res 1920 1080
"@
Set-Content -Path "$dist\start-fullscreen.bat" -Encoding ascii -Value @"
@echo off
rem Red Planet 4.12 - borderless over the whole monitor. -fit sizes the
rem window to the panel AND picks the render size to match the cockpit
rem canvas it lands in, so no -res guessing: the 3D arrives 1:1 instead
rem of being stretched. Pass -res yourself to override it.
rem (Add -egg TEST.EGG to skip the menu and run the canned mission.)
cd /d "%~dp0"
start rpl4opt.exe -fit
"@
Set-Content -Path "$dist\README.txt" -Encoding ascii -Value @"
Red Planet 4.12.3
=================
Run start-fullscreen.bat for borderless over the whole monitor, or
start-windowed.bat to keep a title bar. No cockpit hardware needed.
If there is no sound, run oalinst.exe once.
Controls (XInput controller and/or keyboard) - EVERY input is
rebindable: edit bindings.txt beside the exe (written with the full
documented default layout on first run; delete it to restore).
Left stick / NumPad 8462 joystick (8=fwd 2=back 4=left 6=right)
LT / RT or NumPad 7,9 left / right pedal
Right stick Y throttle (holds position)
Shift / Ctrl throttle up / down
A / Space / NumPad 0 joystick trigger
RB / Alt reverse thrust
DPad / arrow keys joystick hat (look)
9 / 0 keys config buttons (pad Start/Back left free)
Number+letter rows MFD bank buttons (as printed on the panel)
F1-F12 secondary / screen columns
Alt+Q abort the mission (score banked)
The cockpit scales to whatever window you give it - maximise it and it
grows, keeping its 16:9 shape (an ultrawide gets black bars rather than
a stretched cockpit).
The 3D itself renders at whatever -res says and is then stretched onto
the cockpit's viewscreen, so a mismatched -res costs sharpness. -fit
takes care of that for you: it measures the monitor, works out the
canvas the cockpit will settle on, and asks for exactly that render
size, so the picture arrives 1:1.
rpl4opt.exe -fit borderless, res chosen for you
rpl4opt.exe -fit -res 1280 720 same window, lighter render
rpl4opt.exe -windowed -res 2560 1440 pick both yourself
(-windowed-fullscreen is accepted as a long spelling of -fit.)
The full pod cockpit comes up in a single window: three green MFDs
across the top, the 3D viewscreen centered with the orange plasma glass
at its left, and the lower MFDs flanking the portrait map. The red
buttons around each MFD and the amber buttons beside the map are the
pod's real button banks: click them with the mouse, and they light up
as the game commands their lamps.
environ.ini is self-documenting: every option ships in the file with
a comment (Steam networking, keyboard lighting, stick inversion, LAN
hosting, developer keys, display scaling and radar placement, and more).
CONTROLS.html is the full controls map - open it in a browser for the
pad, keyboard and pod-panel diagrams. CONTROLS.txt is the same thing as
plain text.
Known prototype notes: pods race untextured (the player1-8 skins come
from the presets system, not shipped data), and text drawn on the plasma
glass may appear rotated.
Source: https://gitea.mysticmachines.com/VWE/RP412
"@
# --- summary / optional zip ------------------------------------------------
$size = (Get-ChildItem $dist -Recurse | Measure-Object Length -Sum).Sum
Write-Host ("dist ready: {0:N1} MB" -f ($size / 1MB))
if ($Zip) {
$zipPath = Join-Path $root 'RedPlanet-4.12.3.zip'
Write-Host "zipping to $zipPath..."
# Everything lives under a single RP412\ folder inside the zip, so
# unpacking anywhere gives one self-contained game directory instead
# of scattering files into the extraction folder.
$stage = Join-Path ([System.IO.Path]::GetTempPath()) 'rp412-zipstage'
if (Test-Path $stage) { Remove-Item -Recurse -Force $stage }
New-Item -ItemType Directory -Force "$stage\RP412" | Out-Null
Copy-Item "$dist\*" "$stage\RP412" -Recurse -Force
Compress-Archive -Path "$stage\RP412" -DestinationPath $zipPath -Force
Remove-Item -Recurse -Force $stage
}