Two testers reported the map and the countdown clock freezing, one of them only on larger, more complex maps, and one of them until a death. Both details point at the same place. The gauges and the cockpit displays are redrawn in whatever time is left after the 3D view. The background loop is guaranteed a single pass per frame and gets more only while time remains before the frame is due, and one pass drew exactly one gauge. So a full sweep of ninety-odd gauges needed ninety-odd passes - free when there is spare frame, but on a busy map the 3D view eats all of it, the loop drops to its one guaranteed pass, and a sweep takes ninety-odd FRAMES. Seconds. A death makes the renderer skip every static object, the budget frees up, and the backlog drains at once: the display appears to come back to life. Worse, the copy phase that follows ended after a SINGLE display, so the map - one of three - came round only every third sweep. So: draw gauges to a 2ms slice rather than one per pass, which ties the refresh rate to elapsed time instead of to how much spare frame there happened to be; and copy every display before reporting the sweep done. Measured on a deliberately starved frame budget, which reproduces the reported symptom: 0.7 sweeps/s before, 3.1 after. At a normal budget 20/s, against 18-19 before - no cost to the healthy case. RP412GAUGESLICE tunes the slice and 0 restores the old behaviour, which reproduces the 0.7 exactly. RP412GAUGEDIAG=1 logs the rate; watching the screen cannot tell a display that has stopped refreshing from one whose picture simply is not changing, which is what made this hard to see. Also fixes the constructor calling Update() three lines before it initialised mDisplayToUpdate, so the first pass indexed the D3D device and surface arrays with whatever was on the stack. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
533 lines
20 KiB
C++
533 lines
20 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"
|
|
"# 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"
|
|
"# 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"
|
|
"# 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"
|
|
"# 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"
|
|
"# 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;
|
|
}
|