Simulation::ReadUpdateRecord threw away the sender's timestamp and
stamped lastUpdate with its own arrival time. The line carried the
original authors' own note: "HACK - should be based upon
message->timeStamp".
The dead reckoner extrapolates a replicant over
(lastPerformance - lastUpdate), so starting that clock at ARRIVAL rather
than at SEND leaves every remote vehicle exactly one network latency
behind where it should be. On the 1 ms LAN inside an arcade that is
nothing. Over Steam Datagram Relay it is 50-150 ms of positional lag on
every other player - a constant bias, not jitter, and the information
needed to remove it was already in the packet.
The timestamp cannot be used as it stands: both machines run
QueryPerformanceCounter since their own boot, so the two clocks share no
epoch. The offset is estimated per peer instead. Each record gives
sample = ourNow - theirStamp = trueOffset + oneWayLatency
and latency is never negative, so the smallest sample seen is the
closest to the truth. A rolling minimum over 128 samples follows crystal
drift and re-adapts when a route gets slower, rather than being pinned
forever by one lucky packet; a shorter path is believed immediately.
Applied with two clamps: never ahead of our own clock, and never further
back than 500 ms. Past that the packet is stale or the estimate is
wrong, and throwing a vehicle half a second forward does more damage
than the lag being corrected.
Entity::UpdateMessageHandler is the only point on the receive path that
knows whose update this is - records carry a timestamp but not an owner -
so it publishes the sender around the loop, and only for entities
somebody else owns. Offsets are forgotten in CreateMission: the hosts in
the next race are not the hosts in the last one and a HostID gets reused.
RP412NETCLOCK=0 restores the arrival-time behaviour, documented in
environ.ini, so a test machine can compare the two without a rebuild.
The estimate is logged per host when it first settles and whenever it
moves more than 50 ms, which is what a three-machine session should be
read against.
WHAT IS AND IS NOT VERIFIED. A full single-player race runs unchanged -
the path is never entered without replicants, which is the regression
risk that reaches everybody. The behaviour this exists for needs real
latency between real machines and is therefore untested: a two-instance
loopback race would only have exercised the zero-latency case, where the
correction is a no-op by construction. Expect remote vehicles to sit
further forward than before, and watch for overshoot when somebody
changes direction sharply - that is the tradeoff this makes, and the
clamp above is what bounds it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
517 lines
19 KiB
C++
517 lines
19 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"
|
|
"# 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;
|
|
}
|