RP412PHYSICSHZ documented in environ.ini, under TARGETFPS where it belongs, with the three rates worth testing: 25, the arcade pods' rate and the step the original handling was tuned against; 50, the middle road; 100, the smoothest contact response. All three divide the engine's millisecond clock exactly and all three are verified bit-identical across frame rates. The entry says what to feel for - hover bounce, wall hits, how the pod takes a hill crest - and asks for the rate alongside the verdict, because whichever one the testers pick becomes the canonical physics for PC and pods alike. It ships commented out: the default stays the frame-coupled game everyone knows until that decision is made on purpose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
633 lines
25 KiB
C++
633 lines
25 KiB
C++
#include "rpl4.h"
|
|
#pragma hdrstop
|
|
|
|
#include "rpl4environ.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
//########################################################################
|
|
// 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"
|
|
"# Read the keyboard, pad and stick only while the game is the window in\n"
|
|
"# front. The pod was the only thing running on its cabinet, so the\n"
|
|
"# virtual RIO reads the key state directly rather than waiting on the\n"
|
|
"# message pump - which means it reads it whatever is in front, and\n"
|
|
"# switching to another window to type flies the pod around while you\n"
|
|
"# type in it.\n"
|
|
"# 1 controls go neutral when you switch away (default)\n"
|
|
"# 0 read them regardless, as earlier builds did\n"
|
|
"# Any window of the game counts as the game, so clicking an MFD pane or\n"
|
|
"# the plasma glass does not drop your controls. Real RIO cockpit\n"
|
|
"# hardware is unaffected either way - this is the keyboard, pad and\n"
|
|
"# joystick path only.\n"
|
|
"RP412INPUTFOCUS=1\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 <title>=<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"
|
|
"# The physics step, in steps per second - the option under test.\n"
|
|
"#\n"
|
|
"# 0 (the default) is the game as it has always run: the simulation\n"
|
|
"# advances by however long each frame took, so the frame rate IS part\n"
|
|
"# of the physics - a pod at 30 fps falls, corners and collides\n"
|
|
"# differently from one at 144. Any other value here decouples them:\n"
|
|
"# the simulation advances in fixed steps of exactly this rate whatever\n"
|
|
"# the display does, and the SAME race plays out on every machine -\n"
|
|
"# measured bit-identical at 30, 60 and 144 fps.\n"
|
|
"#\n"
|
|
"# The three rates worth testing, all exact on the engine's millisecond\n"
|
|
"# clock:\n"
|
|
"# 25 the arcade pods' rate - the step the original handling was\n"
|
|
"# tuned against, coarsest contact response\n"
|
|
"# 50 the middle road\n"
|
|
"# 100 the smoothest contact and terrain response\n"
|
|
"# (Rates that do not divide 1000 evenly - 60, say - quietly run at the\n"
|
|
"# nearest millisecond step instead; the log says so if you try one.)\n"
|
|
"#\n"
|
|
"# What to feel for against 0: hover bounce, wall hits, how the pod\n"
|
|
"# takes the crest of a hill. Report the rate with the verdict.\n"
|
|
"#RP412PHYSICSHZ=50\n"
|
|
"\n"
|
|
"# How long one background pass may spend drawing cockpit gauges, in\n"
|
|
"# milliseconds. The gauges and the MFD/map displays are redrawn in the\n"
|
|
"# time left over after the 3D view; on a big, busy map there is none\n"
|
|
"# left, and at the original one-gauge-per-pass the map and the countdown\n"
|
|
"# clock could sit frozen for seconds at a time - until something (a\n"
|
|
"# death, say) lightened the 3D view enough for the backlog to drain.\n"
|
|
"# Working to a slice ties the refresh rate to elapsed time instead. Set\n"
|
|
"# 0 for the old behaviour; raise it to favour the displays over frame\n"
|
|
"# rate.\n"
|
|
"RP412GAUGESLICE=2\n"
|
|
"\n"
|
|
"# How many times the map redraws per turn of the gauge rate wheel, 1 to\n"
|
|
"# 16. The renderer gives each gauge one step of a sixteen-step wheel and\n"
|
|
"# a gauge redraws only on its own step, so a map left on one step waits a\n"
|
|
"# whole turn. 16 = redraw on every step (default); 1 = whatever the gauge\n"
|
|
"# data asks for, which is how it behaved before this existed. Each step\n"
|
|
"# costs one map redraw against a pass that runs ninety gauges.\n"
|
|
"RP412MAPRATE=16\n"
|
|
"\n"
|
|
"# 1 = log how many times a second every cockpit display is actually\n"
|
|
"# refreshed, to rpl4.log. Watching the screen cannot tell a display that\n"
|
|
"# has stopped refreshing from one whose picture simply is not changing.\n"
|
|
"#RP412GAUGEDIAG=1\n"
|
|
"\n"
|
|
"# 0 = light the on-screen cockpit buttons on the same slow cadence the\n"
|
|
"# arcade pod's serial hardware used. The lamp state is filled once per\n"
|
|
"# gauge cycle, so under the load described above the lit buttons froze\n"
|
|
"# and flashing ones stalled while the 3D view stayed perfectly smooth.\n"
|
|
"# On by default: the buttons are refreshed every frame instead. Ignored\n"
|
|
"# when real RIO hardware is selected - the pod keeps its own cadence.\n"
|
|
"#RP412LAMPSWEEP=0\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"
|
|
"# Line up each remote player's clock with ours, so their vehicle is\n"
|
|
"# extrapolated from when its update was SENT rather than when it\n"
|
|
"# arrived. Without it every remote pod sits one network latency behind\n"
|
|
"# where it should be - invisible on the 1ms arcade LAN the engine was\n"
|
|
"# written for, a constant 50-150ms of lag over the internet. 0 restores\n"
|
|
"# the old arrival-time behaviour if you want to compare.\n"
|
|
"#RP412NETCLOCK=0\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"
|
|
"# The cabinets ran the game at unity and did all their volume and tone\n"
|
|
"# shaping outside it, in an amplifier and a 3-way crossover. You almost\n"
|
|
"# certainly have neither, so these two stand in for them. Both default\n"
|
|
"# to leaving the mix exactly as the pod played it.\n"
|
|
"\n"
|
|
"# Master volume, 0.0 to 2.0, the amplifier's knob. 1.0 is unity. The\n"
|
|
"# sound effects now carry the pitch, layering and dynamics the original\n"
|
|
"# AWE32 soundbanks ask for, which is a good deal livelier than earlier\n"
|
|
"# 4.12 builds - lower this if the whole thing sits too hot.\n"
|
|
"#\n"
|
|
"# PageUp and PageDown change it while you play, in steps of 0.05, and\n"
|
|
"# whatever you leave it on is written to volume.cfg beside the exe and\n"
|
|
"# used from then on - so this line only decides where a machine that has\n"
|
|
"# never been touched starts out. Delete volume.cfg to come back here.\n"
|
|
"#RP412AUDIOVOLUME=0.8\n"
|
|
"\n"
|
|
"# Bass trim, 0.0 to 1.0, the crossover's low band. 1.0 is the low end\n"
|
|
"# exactly as authored. The soundbanks put real weight under collisions,\n"
|
|
"# engines and explosions - deep layers earlier builds played at the\n"
|
|
"# wrong rate, so they barely sounded at all. Lower this to pull that\n"
|
|
"# back; it eases in below 22kHz of playback rate and reaches full cut\n"
|
|
"# on the deepest layers, leaving the mid and top alone.\n"
|
|
"#\n"
|
|
"# Home and End change it while you play, in steps of 0.05, and what you\n"
|
|
"# leave it on is written to bass.cfg beside the exe and used from then\n"
|
|
"# on - so this line only decides where an untouched machine starts.\n"
|
|
"# Delete bass.cfg to come back here.\n"
|
|
"#RP412AUDIOBASS=0.7\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"
|
|
"# Who transforms the vertices: hw hands it to the GPU, sw does it on the\n"
|
|
"# CPU as this engine always has. There was no hardware to hand it to when\n"
|
|
"# it was written; there is now, and it is not close - on a busy track the\n"
|
|
"# 3D foreground drops from about 17ms a frame to under half a\n"
|
|
"# millisecond, and all of that time goes back to the cockpit displays,\n"
|
|
"# which is what makes the map, the clock and the gauges live rather than\n"
|
|
"# updating every few seconds.\n"
|
|
"# Falls back to sw by itself if the adapter has no hardware T&L. sw is\n"
|
|
"# the way back if a driver's fixed-function lighting or fog looks wrong -\n"
|
|
"# the two are not bit-identical.\n"
|
|
"RP412VERTEXPROC=hw\n"
|
|
"\n"
|
|
"# 0 = present without waiting for the panel's retrace. Costs tearing,\n"
|
|
"# buys latency. Measured to make very little difference to the frame\n"
|
|
"# budget here - the frame is full of work, not waiting - so this is a\n"
|
|
"# preference rather than a fix.\n"
|
|
"#RP412VSYNC=0\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;
|
|
}
|