Packing one into every zip meant a tester who unzipped a new build over their folder got their configuration replaced. bindings.txt has never had that problem, because the exe carries the template and writes the file only when it is absent. environ.ini now works the same way, so a new build can land on an existing folder and every setting survives. The 245-line template moves out of pack-dist.ps1 and into RPL4ENVIRON.cpp as the exe's own literal, which also means the exe alone can produce a working install. It was lifted mechanically rather than retyped, and the file it writes is line-for-line identical to the one we have been shipping - only the line endings changed, from a mongrel 243 LF plus one stray CRLF that PowerShell's Set-Content left on the end, to the uniform LF the game already writes bindings.txt with. It cannot simply become optional. Without environ.ini, 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. So the game writes the file rather than tolerating its absence. The cost of a file that is never overwritten is that a tester carrying one across many builds stops being offered new options. Nothing breaks - an option added later defaults to "behave as before" - but it goes unnoticed, and "the podium does not work" is a confusing bug report when the real answer is that their environ.ini predates RP412PODIUM. So the load names every template key the player's file has never mentioned, and says they are at built-in defaults and that deleting the file brings the documented one back. A stale seven-line file lists all 40. The file is read, never rewritten. The mention test is deliberately generous - a key counts as known if it appears in any form, commented or not - because the failure it guards against is worse than a missed notice: environ.ini is applied line by line, so a second copy of a key appearing later in the file would silently override the player's own. The version line also moves to the top of WinMain. It used to print after the environment was loaded, so the first thing in rpl4.log was a message about environ.ini rather than which build wrote it. Verified: the written file matches the old shipped one line for line; an edited file with a hand-added comment survives another run untouched; a seven-line file from an older build boots and names all 40 options it has never heard of; and a full mission on a self-written file brings up the glass cockpit at 125% with the virtual RIO active and nothing alarming in the log. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
509 lines
18 KiB
C++
509 lines
18 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"
|
|
"# ---- 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;
|
|
}
|