diff --git a/BUILD.md b/BUILD.md index 023357f..32d6650 100644 --- a/BUILD.md +++ b/BUILD.md @@ -74,8 +74,11 @@ outside a git checkout stamps `4.12.x (no repository)` rather than inventing a number that would sort against real ones. **Packaging:** [pack-dist.ps1](pack-dist.ps1) assembles a runnable game into -`dist\` (exe + PDB, game data, OpenAL/libsndfile runtimes, desktop -`environ.ini`, `start-windowed.bat`, README). Pass `-Zip` to also produce +`dist\` (exe + PDB, game data, OpenAL/libsndfile runtimes, launch scripts, +HANDBOOK.html, README). It deliberately does **not** write `environ.ini` — +the exe carries that template and writes it on first run +([RP_L4/RPL4ENVIRON.cpp](RP_L4/RPL4ENVIRON.cpp)), so a tester can drop a new +build over an old folder without losing their settings. Pass `-Zip` to also produce `RedPlanet-.zip` for handing to someone else. It reads the version from `rpl4build.h` rather than asking git again, so the package and the binary inside it cannot disagree, and it warns if the build it is packing diff --git a/README.md b/README.md index c6c0ebc..e825ea9 100644 --- a/README.md +++ b/README.md @@ -96,9 +96,13 @@ team/position columns and its own track list). Steam multiplayer: see [docs/STEAM-3-MACHINE-TEST.md](docs/STEAM-3-MACHINE-TEST.md) (until RP412 has its own AppID it runs under Spacewar, 480). -The two config files beside the exe are self-documenting: **environ.ini** -(every engine option, commented) and **bindings.txt** (every key, pad -button, and axis; written with the full default layout on first run). +The config files beside the exe are self-documenting and none of them +ship: the game writes each one the first time it needs it and then leaves +it alone, so a new build dropped over an existing folder keeps every +setting. **environ.ini** is every engine option, commented; **bindings.txt** +every key, pad button and axis; **pilot.cfg** your callsign and loadout; +**mfd_layout.cfg** where you dragged the windows. Delete any of them to +start that part over with the current defaults. Default controls: numpad flies (8/2/4/6 stick, 7/9 pedals, 0 trigger), Shift/Ctrl throttle, Alt reverse, arrows look, Space fires, letter rows are the MFD button banks as printed on the panel. **Alt+Q** aborts a diff --git a/RP_L4/RPL4.CPP b/RP_L4/RPL4.CPP index f0deb20..c8cbe63 100644 --- a/RP_L4/RPL4.CPP +++ b/RP_L4/RPL4.CPP @@ -22,6 +22,7 @@ #include "rpl4pb.h" #include "rpl4fe.h" +#include "rpl4environ.h" #include "rpl4console.h" #include "rpl4lobby.h" #include "..\munga_l4\l4steamtransport.h" @@ -150,6 +151,15 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine SetUnhandledExceptionFilter(RPL4CrashDumpFilter); + // + // Which build this is, before anything else can fail. The patch number + // is this repository's commit count and the hash beside it names the + // commit, so a log from a test machine says exactly where it came from. + // A trailing '+' means the tree had uncommitted changes when it was + // built. See stamp-version.ps1. + // + DEBUG_STREAM << "Red Planet " << RP412_VERSION_LONG << std::endl << std::flush; + // load up our environment variables //controls if(getenv("L4CONTROLS") == NULL) @@ -164,38 +174,14 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine putenv("TARGETFPS=60"); if(getenv("MAXPARTICLES") == NULL) putenv("MAXPARTICLES=8192"); - FILE *file; - char line[1024]; - if (fopen_s(&file, "environ.ini", "r") == 0) - { - while (!feof(file)) - { - if (fgets(line, sizeof(line), file)) - { - for (int i = strlen(line); i >= 0; i--) - if (line[i] == '\n' || line[i] == '\r') - line[i] = 0; - // the file is self-documenting: skip comments, blanks, - // and anything that is not KEY=VALUE - char *setting = line; - while (*setting == ' ' || *setting == '\t') - ++setting; - if (*setting == '\0' || *setting == '#' || *setting == ';' || - strchr(setting, '=') == NULL) - continue; - putenv(setting); - } - } - fclose(file); - } + // + // environ.ini: written on first run and read here. The exe owns the + // template rather than the packaging script laying one down on every + // unzip, so a tester can drop a new build over an old folder and keep + // their settings. See rpl4environ.h. + // + RPL4Environ_Load(); - // - // The patch number is this repository's commit count and the hash beside - // it names the commit, so a log from a test machine says exactly which - // build it came from. A trailing '+' means the tree had uncommitted - // changes when it was built. See stamp-version.ps1. - // - DEBUG_STREAM << "Red Planet " << RP412_VERSION_LONG << std::endl << std::flush; DEBUG_STREAM << "L4CONTROLS=" << getenv("L4CONTROLS") << std::endl << std::flush; #ifdef RP412_STEAM diff --git a/RP_L4/RPL4ENVIRON.cpp b/RP_L4/RPL4ENVIRON.cpp new file mode 100644 index 0000000..7e17514 --- /dev/null +++ b/RP_L4/RPL4ENVIRON.cpp @@ -0,0 +1,508 @@ +#include "rpl4.h" +#pragma hdrstop + +#include "rpl4environ.h" + +#include +#include +#include + +//######################################################################## +// environ.ini - see rpl4environ.h for why the exe owns this rather than +// the packaging script. +//######################################################################## + +namespace +{ + const char kEnvironFileName[] = "environ.ini"; + + //------------------------------------------------------------------- + // The shipped configuration, verbatim. Lifted out of pack-dist.ps1 + // so there is one source of truth and the exe alone can produce a + // working install. + //------------------------------------------------------------------- + const char kEnvironTemplate[] = +"# ============================================================================\n" +"# environ.ini - Red Planet 4.12 configuration\n" +"# ============================================================================\n" +"# One KEY=VALUE per line, read at game start. Lines starting with # or ;\n" +"# are comments; anything without an = is ignored. Delete a line (or\n" +"# comment it out) to fall back to the built-in default.\n" +"#\n" +"# Input bindings live in bindings.txt beside the exe (written with the\n" +"# full documented layout on first run; delete it to restore defaults).\n" +"#\n" +"# Your callsign and loadout are remembered in pilot.cfg beside the exe.\n" +"# Set them on the setup screen once and they come back every session,\n" +"# however you left - launching, joining a lobby, or quitting. Delete\n" +"# that file to start over.\n" +"\n" +"# ---- Core (the shipped configuration) --------------------------------------\n" +"\n" +"# Control stack: tokens separated by ; or , processed left to right.\n" +"# PAD the virtual RIO (XInput controller + keyboard,\n" +"# rebindable via bindings.txt)\n" +"# RIO real serial cockpit hardware on COM1\n" +"# RIO:COMn same, on another port (RIO:COM3, ...)\n" +"# KEYBOARD the engine keyboard handler\n" +"# MOUSE, JOYSTICK, FLIGHTSTICKPRO, THRUSTMASTER, DIJOYSTICK\n" +"# legacy pointer/joystick drivers (untested here)\n" +"# Unset falls back to KEYBOARD alone.\n" +"L4CONTROLS=PAD;KEYBOARD\n" +"\n" +"# Renderer bring-up argument. Only its presence is checked (the DPL\n" +"# resolution parsing it once fed is gone) and the game refuses to start\n" +"# without it - any non-empty value works. Leave as shipped.\n" +"DPLARG=1\n" +"\n" +"# DPL (renderer/scene) configuration file, searched beside the exe.\n" +"# Any notation file name; RPDPL.INI is the one that ships.\n" +"L4DPLCFG=RPDPL.INI\n" +"\n" +"# Gauge (MFD/instrument) canvas. Must name a page of GAUGE\\L4GAUGE.INI:\n" +"# 640x480x8 | 640x480x16 | 800x600x16\n" +"# Unset disables the gauge renderer (and with it all MFDs).\n" +"L4GAUGE=640x480x16\n" +"\n" +"# Plasma display.\n" +"# SCREEN render the pod's plasma glass in-window (currently\n" +"# parked off-layout)\n" +"# COM1, COM2... drive real plasma glass on that serial port\n" +"# (9600 baud, N81)\n" +"# Unset = no plasma display.\n" +"L4PLASMA=SCREEN\n" +"\n" +"# 0 = classic separate gauge windows; 1 = the single-window glass\n" +"# cockpit (all seven displays composed on a locked 1920x1080 canvas\n" +"# around the viewscreen); 2 = exploded diagnostic view (each display\n" +"# in its own native-resolution desktop window - MFDs 640x480, map\n" +"# 480x640 - decoded exactly as the pod's VDB split them, no downscale).\n" +"L4MFDSPLIT=1\n" +"\n" +"# The game window - and in the exploded view (L4MFDSPLIT=2) each display\n" +"# window - is placed fresh every launch, so moving one somewhere useful\n" +"# never survived the menu-race-menu loop. This remembers where you put\n" +"# them, in mfd_layout.cfg beside this file:\n" +"# off / 0 / unset computed placement only, no file (default)\n" +"# load put the windows back where they were saved\n" +"# save the same, and re-save on every finished drag\n" +"# The game window gets its size back too, so you can size the cockpit to\n" +"# suit your monitor once and keep it. The display windows get position\n" +"# only: their size follows their content and their button banks, so an\n" +"# old one is never restored over them. Arrange everything once with\n" +"# save, then leave it on load.\n" +"#\n" +"# The plasma display window takes part too, under \"Plasma Display\".\n" +"#\n" +"# Each line in mfd_layout.cfg reads =<x>,<y>,<w>,<h>, and you can\n" +"# append ,noframe to take that window's title bar and border off - a\n" +"# cockpit that fills the monitor edge to edge without -fit taking the\n" +"# whole screen. Put the window where you want it first: a bare window\n" +"# has nothing to drag by. Delete the flag to get the frame back.\n" +"#RP412MFDLAYOUT=off\n" +"\n" +"# Size of the six secondary displays in the glass cockpit, as a\n" +"# percentage of their pod size. The pod bolted them down at one size;\n" +"# on a big panel there is room to trade viewscreen for instrument, so\n" +"# turn these up if you want to actually read the other displays while\n" +"# you fly. 100 = as the pod had them. Range 25-200 (out-of-range and\n" +"# unreadable values fall back to the group setting, then to 100).\n" +"#\n" +"# The scaling is applied in canvas units, before the cockpit is fitted\n" +"# to your window, so a given number looks the same on every monitor.\n" +"# The layout stays legal whatever you ask for - the panes are clamped\n" +"# against their actual neighbours, shrinking uniformly so a display\n" +"# never comes out stretched. They do overlap the viewscreen, exactly\n" +"# as the pod's bezels did, but never each other.\n" +"#\n" +"# L4MFDSCALE sets all five green MFDs at once.\n" +"L4MFDSCALE=100\n" +"\n" +"# ...and any single display can override it. Uncomment one to size it\n" +"# on its own - useful if you only care about, say, the damage readout.\n" +"# UL upper left UC upper center UR upper right\n" +"# LL lower left LR lower right\n" +"#L4MFDSCALE_UL=100\n" +"#L4MFDSCALE_UC=100\n" +"#L4MFDSCALE_UR=100\n" +"#L4MFDSCALE_LL=100\n" +"#L4MFDSCALE_LR=100\n" +"\n" +"# The portrait radar/map, sized on its own (it already sits at 1.35x\n" +"# the MFDs by default). It shares the canvas with whichever MFD is\n" +"# above it, so at extreme settings one of the two gives way.\n" +"L4RADARSCALE=100\n" +"\n" +"# Where the radar sits:\n" +"# CENTER bottom centre, under the viewscreen, as the pod had it\n" +"# (default; BOTTOM and CENTRE mean the same)\n" +"# LEFT bottom left corner (or BOTTOMLEFT)\n" +"# RIGHT bottom right corner (or BOTTOMRIGHT)\n" +"# MIDLEFT left edge, halfway up (or LEFTCENTER / LEFTCENTRE)\n" +"# MIDRIGHT right edge, halfway up (or RIGHTCENTER / RIGHTCENTRE)\n" +"# Anywhere but CENTER stops it blocking the middle of the road, which\n" +"# is worth having on a wide screen.\n" +"#\n" +"# In a bottom corner it is one of three panes along the bottom, and the\n" +"# lower MFD whose corner it takes slides inboard beside it. Halfway up\n" +"# a side it leaves the bottom row entirely and sits between that side's\n" +"# two MFDs - roomy on a tall radar, but if the MFDs on that side are\n" +"# also scaled up, the radar is the one that gives way (it has to clear\n" +"# both of them, and it grows from the middle in both directions).\n" +"L4RADARPOS=CENTER\n" +"\n" +"# The Winners Circle: at the end of a race the finishers are stood on\n" +"# the award platform in finishing order, with each pilot's callsign on\n" +"# the plate beside their spot, and held there for a few seconds before\n" +"# the results screen. 1 = show it, 0 = straight to the results.\n" +"RP412PODIUM=1\n" +"\n" +"# The shot is framed for you, but these move the camera if you want it\n" +"# somewhere else. Distances are in game units, measured from the middle\n" +"# of the group of finishers.\n" +"# STANDOFF how far out in front of the stand the camera sits\n" +"# HEIGHT how far above the group\n" +"# AIM height of the point it looks at, relative to the group -\n" +"# negative tilts down, positive tilts up\n" +"# ASPECT the stand was composed for a 4:3 pod monitor, so the shot\n" +"# is cropped to that shape with black either side. 0 runs it\n" +"# full width instead.\n" +"# FADEIN seconds to come up out of the black after the race fades\n" +"# CAM 0 watches from your own cockpit rather than off the stand\n" +"#RP412PODIUMSTANDOFF=36\n" +"#RP412PODIUMHEIGHT=12\n" +"#RP412PODIUMAIM=2\n" +"#RP412PODIUMASPECT=1.333\n" +"#RP412PODIUMFADEIN=0.45\n" +"#RP412PODIUMCAM=1\n" +"\n" +"# Override the game length the menu picked, in seconds. The shortest the\n" +"# menu offers is 3:00, which is a long wait when what you are testing is\n" +"# what happens at the buzzer. Unset = use the menu's choice.\n" +"#RP412MISSIONSECONDS=20\n" +"\n" +"# Simulation/render frame rate, integer frames/second. The desktop\n" +"# default is 60; the arcade pods shipped at 25.\n" +"TARGETFPS=60\n" +"\n" +"# 1 = Steam networking (lobbies, FakeIP mesh). Needs the Steam client\n" +"# running and steam_appid.txt beside the exe; without them the game\n" +"# logs the reason and falls back to plain TCP. 0 = TCP only.\n" +"RP412STEAM=1\n" +"\n" +"# ---- Optional ---------------------------------------------------------------\n" +"\n" +"# RGB keyboard lamp mirror (Windows Dynamic Lighting): keys bound to\n" +"# lamp buttons glow with the panel, flash modes and all.\n" +"# Unset or nonzero = on (the default); 0 = off.\n" +"#RP412KEYLIGHT=0\n" +"\n" +"# Invert the stick on top of whatever bindings.txt produces:\n" +"# X = invert X only, Y = invert Y only, XY = both (case-insensitive).\n" +"#L4PADFLIP=XY\n" +"\n" +"# Anti-aliasing sample count, passed straight to Direct3D 9:\n" +"# 0 = off, else 2..16 as the GPU supports (1 selects the driver's\n" +"# \"nonmaskable\" mode; unsupported counts fail device creation).\n" +"#MULTISAMPLE=0\n" +"\n" +"# Particle budget, integer. Default 8192.\n" +"#MAXPARTICLES=8192\n" +"\n" +"# On-screen plasma glass (L4PLASMA=SCREEN only). SCALE = integer pixel\n" +"# size 1..16, default 4 (out-of-range values are ignored). POS = window\n" +"# top-left as X,Y screen coordinates; unset = auto, parked below the\n" +"# main window.\n" +"#L4PLASMASCALE=4\n" +"#L4PLASMAPOS=0,0\n" +"\n" +"# Fixed random seed (repeatable runs): any unsigned integer.\n" +"# Unset seeds from the clock.\n" +"#RANDOM=12345\n" +"\n" +"# ---- LAN play without Steam -------------------------------------------------\n" +"# Host a race over plain TCP: list the member pods' console channels\n" +"# (members run: rpl4opt.exe -windowed -res 1920 1080 -net 1501).\n" +"# RP412HOSTPODS comma-separated IP[:port] list, one entry per member\n" +"# pod; port defaults to 1501 per entry\n" +"# RP412HOSTPORT this machine's console port, integer > 0\n" +"# (default 1501)\n" +"# RP412HOSTADDR this machine's LAN IP as members can reach it\n" +"# (default 127.0.0.1)\n" +"#RP412HOSTPODS=192.168.1.20:1501,192.168.1.21:1501\n" +"#RP412HOSTPORT=1501\n" +"#RP412HOSTADDR=192.168.1.10\n" +"\n" +"# ---- Developer / testing ----------------------------------------------------\n" +"\n" +"# Nonzero arms the debug keys: Alt+W wireframe, Alt+V predator vision,\n" +"# Alt+F frame dump, Alt+/ perf stats, Alt+E event-queue dump.\n" +"# 0 or unset = off. (Alt+Q, the mission abort, is always live.)\n" +"#RP412DEVKEYS=1\n" +"\n" +"# Console race-length override, integer seconds (short test races).\n" +"# Values <= 0 are ignored.\n" +"#L4CONSOLELEN=30\n" +"\n" +"# Nonzero = Steam transport loopback self-test at boot (logs PASS/FAIL).\n" +"#RP412STEAMSELFTEST=1\n" +"\n" +"# ---- Arcade heritage (multi-monitor pods; not used on the desktop) ----------\n" +"# PRIMGAUGE / SECGAUGE / MFDGAUGE / MFDGAUGE2 pin a display to a monitor\n" +"# by adapter index (0, 1, 2...). SPANDISABLE: 0 = let the MFDs span one\n" +"# wide surface, nonzero = separate windows (setting MFDGAUGE2 alone also\n" +"# forces spanning off). L4EYES = \"x y z xrot yrot zrot [type]\" floats\n" +"# for a detached camera; a type starting with r offsets it relative to\n" +"# the pod. L4INTERCOM enables the crew intercom - only its presence\n" +"# matters (traditionally COM2). NOMODES skips the mode/lamp programming;\n" +"# presence alone triggers it, even NOMODES=0. LOGSIZE > 0 sizes the\n" +"# trace log in dev builds compiled with tracing.\n" +"#PRIMGAUGE=1\n" +"#SECGAUGE=2\n" +"#MFDGAUGE=3\n" +"#MFDGAUGE2=4\n" +"#SPANDISABLE=1\n" +"#L4EYES=1\n" +"#L4INTERCOM=COM2\n" +"#NOMODES=1\n" +"#LOGSIZE=1000000\n" + ; + + //------------------------------------------------------------------- + // Does the player's file mention this key at all - set, or commented + // out, or with whitespace in front of it? + // + // Deliberately generous: a key that is mentioned in ANY form is left + // alone. The alternative failure is worse than a missed notice, since + // environ.ini is applied line by line and a second copy of a key + // further down the file would silently override the player's own. + //------------------------------------------------------------------- + Logical FileMentionsKey(const char *text, const char *key, int key_length) + { + const char *cursor = text; + while ((cursor = strstr(cursor, key)) != NULL) + { + // + // Must be a whole key: preceded by start-of-line, whitespace + // or a comment mark, and followed by '='. + // + const char *after = cursor + key_length; + Logical starts_token = + (cursor == text) || + (cursor[-1] == '\n') || (cursor[-1] == '\r') || + (cursor[-1] == ' ') || (cursor[-1] == '\t') || + (cursor[-1] == '#') || (cursor[-1] == ';'); + if (starts_token) + { + const char *scan = after; + while (*scan == ' ' || *scan == '\t') + { + ++scan; + } + if (*scan == '=') + { + return True; + } + } + cursor = after; + } + return False; + } + + //------------------------------------------------------------------- + // Name every template key the player's file has never heard of. Not + // a fix - their file stays theirs - but it puts the reason for a + // missing feature in the log we already ask testers for. + //------------------------------------------------------------------- + void ReportUnmentionedKeys(const char *file_text) + { + char missing[1024]; // what gets printed + char seen[1024]; // the same keys as "KEY=", so the mention + // test above can dedupe against them + missing[0] = '\0'; + seen[0] = '\0'; + int count = 0; // how many are missing + int listed = 0; // how many fitted in the line + + const char *cursor = kEnvironTemplate; + while (*cursor != '\0') + { + const char *line = cursor; + const char *end = strchr(line, '\n'); + int length = (end != NULL) ? (int)(end - line) : (int) strlen(line); + cursor = (end != NULL) ? (end + 1) : (line + length); + + // + // A template key line is "KEY=..." or "#KEY=..." - the + // commented ones are options that ship switched off, and a + // player who has never seen them wants to know they exist. + // + const char *scan = line; + int remaining = length; + if (remaining > 0 && *scan == '#') + { + ++scan; + --remaining; + } + if (remaining <= 0 || !(isalpha((unsigned char) *scan) || *scan == '_')) + { + continue; + } + int key_length = 0; + while (key_length < remaining && + (isalnum((unsigned char) scan[key_length]) || scan[key_length] == '_')) + { + ++key_length; + } + if (key_length >= remaining || scan[key_length] != '=' || key_length > 60) + { + continue; + } + + char key[64]; + memcpy(key, scan, key_length); + key[key_length] = '\0'; + + if (FileMentionsKey(file_text, key, key_length)) + { + continue; + } + // + // Templates list some keys twice (documented once, shown + // again in an example); do not name one twice. + // + if (FileMentionsKey(seen, key, key_length)) + { + continue; + } + ++count; + if (strlen(seen) + key_length + 3 < sizeof(seen)) + { + strcat(seen, key); + strcat(seen, "=\n"); + } + if (strlen(missing) + key_length + 3 < sizeof(missing)) + { + if (missing[0] != '\0') + { + strcat(missing, ", "); + } + strcat(missing, key); + ++listed; + } + } + + if (count > 0) + { + // + // Say when the list is short of the count rather than letting + // a full buffer quietly shorten the answer. + // + DEBUG_STREAM << "Environ: " << kEnvironFileName << " does not mention " + << count << " option(s) this build knows: " << missing; + if (listed < count) + { + DEBUG_STREAM << ", and " << (count - listed) << " more"; + } + DEBUG_STREAM << "\nEnviron: they are at their built-in defaults - delete " + << kEnvironFileName << " to get the documented file back\n" + << std::flush; + } + } +} + +void + RPL4Environ_Load() +{ + // + // First run: lay down the documented default. From here on the file + // belongs to whoever is sitting at this machine. + // + FILE *file = fopen(kEnvironFileName, "rb"); + if (file == NULL) + { + FILE *out = fopen(kEnvironFileName, "wb"); + if (out != NULL) + { + fwrite(kEnvironTemplate, 1, strlen(kEnvironTemplate), out); + fclose(out); + DEBUG_STREAM << "Environ: wrote default " << kEnvironFileName + << "\n" << std::flush; + } + else + { + DEBUG_STREAM << "Environ: could not write " << kEnvironFileName + << " - running on built-in defaults\n" << std::flush; + } + file = fopen(kEnvironFileName, "rb"); + } + if (file == NULL) + { + return; + } + + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + if (size <= 0) + { + fclose(file); + return; + } + char *text = new char[size + 1]; + size_t read = fread(text, 1, size, file); + text[read] = '\0'; + fclose(file); + + // + // One KEY=VALUE per line. Comments, blanks and anything without an + // '=' are skipped; everything else goes into the environment, which + // is why a line here beats a variable set in the shell. + // + int applied = 0; + char line[1024]; + const char *cursor = text; + while (*cursor != '\0') + { + int length = 0; + while (cursor[length] != '\0' && cursor[length] != '\n' && + length < (int) sizeof(line) - 1) + { + line[length] = cursor[length]; + ++length; + } + line[length] = '\0'; + cursor += length; + while (*cursor == '\n' || *cursor == '\r') + { + ++cursor; + } + for (int i = length - 1; i >= 0; --i) + { + if (line[i] == '\r' || line[i] == '\n') + { + line[i] = '\0'; + } + } + + char *setting = line; + while (*setting == ' ' || *setting == '\t') + { + ++setting; + } + if (*setting == '\0' || *setting == '#' || *setting == ';' || + strchr(setting, '=') == NULL) + { + continue; + } + putenv(setting); + ++applied; + } + + DEBUG_STREAM << "Environ: " << applied << " setting(s) from " + << kEnvironFileName << "\n" << std::flush; + + ReportUnmentionedKeys(text); + + delete[] text; +} diff --git a/RP_L4/RPL4ENVIRON.h b/RP_L4/RPL4ENVIRON.h new file mode 100644 index 0000000..185ceae --- /dev/null +++ b/RP_L4/RPL4ENVIRON.h @@ -0,0 +1,44 @@ +//===========================================================================// +// File: rpl4environ.h // +// Project: MUNGA Brick: Red Planet LBE Application // +// Contents: environ.ini - written on first run, then the player's // +//---------------------------------------------------------------------------// +// Copyright (C) 1994-1995, Virtual World Entertainment, Inc. // +// PROPRIETARY AND CONFIDENTIAL // +//===========================================================================// + +#pragma once + +#include "..\munga\style.h" + +//######################################################################## +// +// environ.ini is the game's configuration: one KEY=VALUE per line, read +// once at startup and pushed into the environment, so every option the +// engine reads through getenv can be set from a file a player can open. +// +// The exe owns the template and writes it when the file is absent, the +// same way bindings.txt works, rather than the packaging script laying +// one down on every unzip. That is what lets a tester drop a new build +// over an old folder and keep their settings: the file is theirs from +// the moment it exists, and nothing overwrites it. +// +// It cannot simply be optional. Without it L4GAUGE is unset, which +// disables the gauge renderer and takes every MFD with it, and +// L4MFDSPLIT is unset, which is the packed-window arcade layout rather +// than the glass cockpit. The shipped values are the desktop game; the +// built-in getenv fallbacks are the 1995 pod. +// +// The cost of a file that is never overwritten is that a tester carrying +// one across many builds stops being offered new options. Options added +// later default to "behave as before", so nothing breaks - but it does +// go unnoticed, so the load names any template key the player's file +// does not mention. That line in rpl4.log is what turns "the podium does +// not work" into "your environ.ini predates RP412PODIUM". +// +//######################################################################## + +// Write environ.ini if it is not there, then read it into the +// environment. Call once, before anything reads a setting. +void + RPL4Environ_Load(); diff --git a/RP_L4/RP_L4.vcxproj b/RP_L4/RP_L4.vcxproj index 86f02c8..fb06fe5 100644 --- a/RP_L4/RP_L4.vcxproj +++ b/RP_L4/RP_L4.vcxproj @@ -118,6 +118,7 @@ <ClCompile Include=".\RPL4APP.cpp" /> <ClCompile Include=".\RPL4CONSOLE.cpp" /> <ClCompile Include=".\RPL4FE.cpp" /> + <ClCompile Include=".\RPL4ENVIRON.cpp" /> <ClCompile Include=".\RPL4LOBBY.cpp" /> <ClCompile Include=".\RPL4ARND.cpp" /> <ClCompile Include=".\RPL4GAUG.cpp" /> @@ -156,6 +157,7 @@ <ClInclude Include=".\RPL4APP.h" /> <ClInclude Include=".\RPL4CONSOLE.h" /> <ClInclude Include=".\RPL4FE.h" /> + <ClInclude Include=".\rpl4environ.h" /> <ClInclude Include=".\RPL4LOBBY.h" /> <ClInclude Include=".\RPL4ARND.h" /> <ClInclude Include=".\RPL4GAUG.h" /> diff --git a/docs/rp412-handbook.html b/docs/rp412-handbook.html index 8e5c122..3f4e82a 100644 --- a/docs/rp412-handbook.html +++ b/docs/rp412-handbook.html @@ -1316,9 +1316,11 @@ <section> <h2>Files beside the exe</h2> <p class="sub"> - Four files in the game folder are yours. Only one of them ships — the - game writes the other three when it first needs them, so a fresh unzip - has none of them and nothing is lost by deleting one. + Four files in the game folder are yours. <b>None of them ship.</b> The + game writes each one the first time it needs it and then never touches + it again, so a new build dropped over this folder keeps everything you + have set — and deleting any of them simply starts that part over with + the current defaults. </p> <div class="tbl-scroll"> @@ -1329,8 +1331,9 @@ <th class="mono">environ.ini</th> <td> Every engine option, commented in place — displays, renderer, - Steam, the podium, the lot. The only one that ships, and the one - to read first. + Steam, the podium, the lot. The one to read first. Written on + first run; without it the MFDs do not come up at all, so the + game will always put one back. </td> </tr> <tr> @@ -1363,10 +1366,12 @@ <strong>Two that catch people out.</strong> <code>environ.ini</code> is applied <i>over</i> the environment, so a variable you set in a shell loses to an uncommented line in the file — - comment the line out rather than fighting it. And - <code>bindings.txt</code> is never overwritten once it exists, which - is what protects your edits: after an update, <b>delete it</b> to pick - up new default bindings. + comment the line out rather than fighting it. And nothing here is ever + overwritten once it exists, which is exactly what lets you keep a + folder across builds — but it means a file carried through several + updates stops being offered new options. The game names any it has not + heard of in <code>rpl4.log</code>; <b>delete the file</b> to get the + fully documented current one back. </p> </div> diff --git a/pack-dist.ps1 b/pack-dist.ps1 index a76babe..5e77272 100644 --- a/pack-dist.ps1 +++ b/pack-dist.ps1 @@ -8,7 +8,7 @@ # 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) +# (environ.ini is NOT shipped - the exe writes it on first run) # - start/joyconfig scripts, HANDBOOK.html, CONTROLS.txt and a README # # Usage: powershell -ExecutionPolicy Bypass -File pack-dist.ps1 [-Zip] @@ -134,252 +134,12 @@ if (Test-Path $openal) { } # --- 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). -# -# Your callsign and loadout are remembered in pilot.cfg beside the exe. -# Set them on the setup screen once and they come back every session, -# however you left - launching, joining a lobby, or quitting. Delete -# that file to start over. - -# ---- 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 - -# The game window - and in the exploded view (L4MFDSPLIT=2) each display -# window - is placed fresh every launch, so moving one somewhere useful -# never survived the menu-race-menu loop. This remembers where you put -# them, in mfd_layout.cfg beside this file: -# off / 0 / unset computed placement only, no file (default) -# load put the windows back where they were saved -# save the same, and re-save on every finished drag -# The game window gets its size back too, so you can size the cockpit to -# suit your monitor once and keep it. The display windows get position -# only: their size follows their content and their button banks, so an -# old one is never restored over them. Arrange everything once with -# save, then leave it on load. -# -# The plasma display window takes part too, under "Plasma Display". -# -# Each line in mfd_layout.cfg reads <title>=<x>,<y>,<w>,<h>, and you can -# append ,noframe to take that window's title bar and border off - a -# cockpit that fills the monitor edge to edge without -fit taking the -# whole screen. Put the window where you want it first: a bare window -# has nothing to drag by. Delete the flag to get the frame back. -#RP412MFDLAYOUT=off - -# 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 - -# The Winners Circle: at the end of a race the finishers are stood on -# the award platform in finishing order, with each pilot's callsign on -# the plate beside their spot, and held there for a few seconds before -# the results screen. 1 = show it, 0 = straight to the results. -RP412PODIUM=1 - -# The shot is framed for you, but these move the camera if you want it -# somewhere else. Distances are in game units, measured from the middle -# of the group of finishers. -# STANDOFF how far out in front of the stand the camera sits -# HEIGHT how far above the group -# AIM height of the point it looks at, relative to the group - -# negative tilts down, positive tilts up -# ASPECT the stand was composed for a 4:3 pod monitor, so the shot -# is cropped to that shape with black either side. 0 runs it -# full width instead. -# FADEIN seconds to come up out of the black after the race fades -# CAM 0 watches from your own cockpit rather than off the stand -#RP412PODIUMSTANDOFF=36 -#RP412PODIUMHEIGHT=12 -#RP412PODIUMAIM=2 -#RP412PODIUMASPECT=1.333 -#RP412PODIUMFADEIN=0.45 -#RP412PODIUMCAM=1 - -# Override the game length the menu picked, in seconds. The shortest the -# menu offers is 3:00, which is a long wait when what you are testing is -# what happens at the buzzer. Unset = use the menu's choice. -#RP412MISSIONSECONDS=20 - -# 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 -"@ +# --- desktop configuration ------------------------------------------------- +# environ.ini is NOT shipped. The exe carries the template and writes it on +# first run (RPL4ENVIRON.cpp), the same way it writes bindings.txt - so a +# tester can drop a new build over an old folder and keep every setting they +# have changed. Laying one down here would overwrite their file on every +# unzip, which is the whole problem. Set-Content -Path "$dist\start-windowed.bat" -Encoding ascii -Value @" @echo off