From 2a5a387381ff907892fe952a3b205fec2f457c07 Mon Sep 17 00:00:00 2001 From: Cyd Date: Tue, 28 Jul 2026 13:16:11 -0500 Subject: [PATCH] Console: remember Enable Custom Bitmaps, ship the art; bump suite to 4.11.4.5 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settings -> Enable Custom Bitmaps was a static bool with no backing store: it defaulted to off on every launch, so an operator had to re-tick it each session and any custom plasma art was silently ignored until they did. New ConsoleSettings persists machine-level menu toggles as XML in %ProgramData%\Tesla Console\console.settings, alongside RPDefaults.rpd / BTDefaults.btd / local.siteconfig. It is loaded once from Main and never from a static initializer: the differential suite drives PlasmaBitmaps directly and must keep seeing the original defaults rather than whatever this machine has saved. A missing file is the first-run case; a corrupt one is ignored and rewritten by the next toggle, because losing a menu setting must never stop the console starting. Custom art is now version-controlled and rolls with the release. New Console\Plasma Images\ is copied into the package, and the lookup searches %ProgramData%\Tesla Console\Plasma Images first and the exe-relative folder second, so a release can never clobber a site's own name bitmaps. install.bat creates the data-dir folder before the icacls grant so an unelevated operator can write it. Ships with three 128x32 name bitmaps (Deadmeat, Muerte, Phrogg); Muerte arrived as "Muerte_128x32-2.bmp", a name the lookup can never build, so it is renamed to match its pilot. Two fixes fell out of making the flag sticky. Path.Combine ran on the raw participant name outside the try block, so with the option on a pilot named "A:B" threw ArgumentException straight out of egg generation; names that cannot be a Windows file name now just render procedurally. And the art was loaded with Image.FromFile, which keeps the bitmap backed by the file and locked for its whole lifetime — it is copied out through a stream now, so art can be swapped between missions without restarting the console. Verified against the built net40 exe: all three shipped bitmaps resolve by pilot name, ProgramData wins over the shipped copy, a wrong-size file is ignored, an illegal-character name does not throw, the file is not left locked, and the settings round-trip and corrupt-file tolerance both hold. Diff suite 106/106. Version bumped 4.11.4.4 -> 4.11.4.5 across Console, Launcher, vPOD, the install/build banners, the diff-suite version assertion and the README's latest-release pointer. Co-Authored-By: Claude Opus 5 (1M context) --- Console/Plasma Images/Deadmeat_128x32.bmp | Bin 0 -> 576 bytes Console/Plasma Images/Muerte_128x32.bmp | Bin 0 -> 576 bytes Console/Plasma Images/Phrogg_128x32.bmp | Bin 0 -> 5176 bytes Console/Plasma Images/README.md | 60 +++++++++++++ Console/Properties/AssemblyInfo.cs | 4 +- Console/README.md | 11 ++- Console/TeslaConsole.csproj | 6 ++ Console/TeslaConsole/ConsoleSettings.cs | 79 ++++++++++++++++++ Console/TeslaConsole/PlasmaBitmaps.cs | 71 ++++++++++++---- Console/TeslaConsole/Program.cs | 2 + Console/TeslaConsole/TeslaConsoleForm.cs | 1 + Console/install.bat | 7 +- .../BehavioralEquivalenceTests.cs | 2 +- Launcher/TeslaLauncher.csproj | 2 +- Launcher/build.bat | 2 +- Launcher/install.bat | 2 +- README.md | 2 +- vPOD/vPOD.csproj | 4 +- 18 files changed, 229 insertions(+), 26 deletions(-) create mode 100644 Console/Plasma Images/Deadmeat_128x32.bmp create mode 100644 Console/Plasma Images/Muerte_128x32.bmp create mode 100644 Console/Plasma Images/Phrogg_128x32.bmp create mode 100644 Console/Plasma Images/README.md create mode 100644 Console/TeslaConsole/ConsoleSettings.cs diff --git a/Console/Plasma Images/Deadmeat_128x32.bmp b/Console/Plasma Images/Deadmeat_128x32.bmp new file mode 100644 index 0000000000000000000000000000000000000000..10fafdcfad9f71241182e32f5148bc969e106461 GIT binary patch literal 576 zcmY+AF-Sr|7{^Tqp`oCqq2f6<7EOitq@b3fC1`92Y>Wsq&^#fiO}D}V9U5wAsfY$a z3x@|eI8<0fPr<=WtHpP|_wL`_6JO8o_`du9{@;CYck?Sjx66tEuai6zJbgR|_!8mf z{o+tJErS;TmgIXc7!t#J5BP2(S2dl?qKcCl(?9!|>R--zjM0qZm`B}^N1d!QQYknL zK3qvLkL~=1LhFZ`A1KF>A;{8n`t<1$`?HEanw;8A%B0iKytw%&jZYsf+?{L?x(WyE z%utB;Fd~fBn~GXY;;PHIQ=xgvqD4e8!z!=;U&d-fZQ5Rb5DXRo6951I2Qz^*1RN9%4Gm%BhjITO z`1kJLKL!N9ffXdr4wr9W-oO996|#J6*cApOdA5J=-XZB{U=0tsgUsjO{O1RQJ={D7 z_TZ2Y4*oFyzXs+{ArC6v!}$&jg$xd%=`j8uiw2`rjsG9R`2YWZ5Mgi(4LPc zpAA2o00)E9`_GRH{uDp#laZ5c7hw@l4GsKO@T2nKI~hBf_SqFXfB*jpy6n z9vT7W?*poD%Ua&A{O-q(0El_=?WOGJ8AAiW{C6^DvhBO{-!I(%Veo z(1-^gepEc{lPHpGlj?6i`0nF@f&v3a9>Gt7EF%1j2TLOn{{7Y8@KXLGjQ=Op&7I*O kD_s6h)+!~YF6MTa{J+p}0S0D9MmS$<4M&4+Sr6^sxG-b+^p={Z*xVyVku3S0FmoHC+3KghWu_Bc!Ribj`%2cURg{oDn zQmtAws#mX$hld9>YSf@+&6?DzRg2oSYg4CA9qQJtOTBvas9(Q64H`6{VZ(-adV12R zQ6m~RZcLLVO=#M*Db1QSqj~e@v}nC=b4 zef!d{UqAZy@6Uh%0~k1PAcF=CV({R>3>h*6A0Hov4jqcGuP?)f4P*H5;rRLa;qULy zh!G1;EC`OMS&6qJ`7&~?>(;Gf{rdH6*sy_( z8#l6P(DP*4y@jvV3W(W4wYc8uf4k8|S02~M6o$*EJPIDPswXU?4A z?Af!NJ9m!r=g)KD!UZl~yvU_Xm$-cSGFPr#;p)|^T)TFS>({Rn930Gz8#lOl^Cq`$ z-QxD`+uXTxhr4(0a_`eVY=zkbb|H*a|R_AT$;z2p7+_k8&9fsY?Q z^6AqjK7anqmoH!V`t>W{zJ25S_wW4p@k9GJ5Q}4PAod3SzzrCFSDGOkYSsq$2Qs3w zFb$K_bg1YYeye7dCFSc-zZDT}v$$Sktdy@dsFcbWLDUXSXbq`A%~2F(l*TyAq$wqT z+&Yv=Qk3s6NyWfW7f2H87@Jdu!|PCHNs7dbHTG$8M53ZJcNPLsiNSm&MC?$_7nFK_ z6G~cWhd8Q+6{H9f$RN}#RA>Z}OLUYbMTVkFQxT+UB}^rqpas-v(xEgdGE|(@Nkd9d z;Z2a~LRA=nNs|sG)wM%u7n4-f>r)iQUd=*TMlxxHdR9_dss6&*M4*0uM2||zNt%~YA`^<* z#7c=%fnh$tjPF^JS1s=yGY*;EWh%hqaa6p+||FFz_( UokhIPmsQ?z>?`&L{s%Yk7i+~K(EtDd literal 0 HcmV?d00001 diff --git a/Console/Plasma Images/README.md b/Console/Plasma Images/README.md new file mode 100644 index 0000000..2c4b25a --- /dev/null +++ b/Console/Plasma Images/README.md @@ -0,0 +1,60 @@ +# Plasma Images + +Hand-made art for the pods' plasma name displays. Anything dropped in here is +version-controlled and rolls out with the release: `build-package.bat` copies the +folder into `App\`, and `install.bat` puts it beside `TeslaConsole.exe`. + +The folder ships empty on purpose — populate it per site. + +## How it works + +When the console builds a mission egg it renders every participant name into a +1-bit-per-pixel bitmap in two sizes and embeds it in the egg. By default those are +drawn procedurally: `Microsoft Sans Serif`, white on black, no anti-aliasing, the +point size stepped down from 24 until the text fits. + +With **Settings → Enable Custom Bitmaps** ticked, the console looks for a matching +file here first and only falls back to the procedural renderer when there isn't +one. The toggle is remembered in `console.settings` (see below), so it survives a +restart. + +## File naming + +``` +_x.bmp +``` + +The `` part is matched **exactly** against the string being rendered — the +pilot/player name, the Red Planet football team name, or the literal `Camera` for +camera pods. Matching is per-name, not global, so you can override one name and +let the rest render normally. + +Two sizes are needed to cover a name completely; supplying only one is fine, the +other falls back to the font renderer: + +| Size | Egg section | Example | +| -------- | ------------------ | ---------------------- | +| 128 × 32 | `BitMap::Large::…` | `Camera_128x32.bmp` | +| 64 × 16 | `BitMap::Small::…` | `Camera_64x16.bmp` | + +Rules: + +- **Dimensions must match exactly.** A 130×32 file is ignored, silently, and the + name renders procedurally instead. +- **Only brightness matters.** Every pixel at or above 50% brightness lights up on + the plasma; everything else is dark. Colour art gets thresholded, so design in + black and white. +- Any name that cannot be a Windows file name (`:`, `\`, `|`, `?`, `*`, …) can't + have an override — those names always render procedurally. +- Files are read at egg-build time and copied into memory, not locked, so art can + be swapped between missions without restarting the console. + +Preview how a name looks before a mission with the console's **Plasma Font Tool**, +which renders through the same path — with the option on it shows the override. + +## Where the console looks + +1. `%ProgramData%\Tesla Console\Plasma Images\` — this machine's own art. Takes + precedence, and survives a reinstall, so a site can keep local overrides that + a release will not overwrite. +2. `Plasma Images\` next to `TeslaConsole.exe` — this folder, as shipped. diff --git a/Console/Properties/AssemblyInfo.cs b/Console/Properties/AssemblyInfo.cs index 3afa266..96ce320 100644 --- a/Console/Properties/AssemblyInfo.cs +++ b/Console/Properties/AssemblyInfo.cs @@ -8,9 +8,9 @@ using System.Runtime.InteropServices; [assembly: AssemblyCopyright("Copyright © 2009")] [assembly: AssemblyConfiguration("")] [assembly: Guid("581ca4b6-a91c-4d24-b9b5-207f3b5da379")] -[assembly: AssemblyFileVersion("4.11.4.4")] +[assembly: AssemblyFileVersion("4.11.4.5")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: AssemblyTitle("Tesla Console")] [assembly: AssemblyDescription("All code and UI property of Virtual World Entertainment.\r\n\r\nDeveloped by Elsewhen Studios, LLC in association with VGCorps, LLC.\r\n\r\nElsewhen Studios and the Elsewhen Wormhole are trademarks of Elsewhen Studios, LLC\r\n\r\nIncludes the WeifenLuo DockingPane library. Copyright © 2007 Weifen Luo (email: weifenluo@yahoo.com). Licensed under the MIT License - details can be found in WeifenLuo.txt included in this installation.")] -[assembly: AssemblyVersion("4.11.4.4")] +[assembly: AssemblyVersion("4.11.4.5")] diff --git a/Console/README.md b/Console/README.md index 18f8536..ee6a931 100644 --- a/Console/README.md +++ b/Console/README.md @@ -103,9 +103,16 @@ dependency DLLs copied alongside it). - `RedPlanet\RPConfig.xml`, `RedPlanet\RPStrings.xml` are loaded relative to the exe and are copied to the build output automatically. -- `Plasma Images\*.bmp` (under `%ProgramData%`) is an **optional** override set; - when absent the console renders plasma-display text procedurally, so it is not +- `Plasma Images\*.bmp` is an **optional** override set for the pod plasma name + displays, enabled by *Settings → Enable Custom Bitmaps*. The console looks in + `%ProgramData%\Tesla Console\Plasma Images` (the machine's own art, wins) and + then in `Plasma Images\` next to the exe (the set that ships with the release — + see [`Plasma Images/README.md`](Plasma%20Images/README.md) for naming and sizes). + When neither has a match the console renders the text procedurally, so no art is required to build or run. +- Machine-level Settings-menu toggles persist in + `%ProgramData%\Tesla Console\console.settings` (XML, written on change by + `ConsoleSettings`). Deleting it just restores the defaults. ## Notes diff --git a/Console/TeslaConsole.csproj b/Console/TeslaConsole.csproj index 0414c2c..58c52ed 100644 --- a/Console/TeslaConsole.csproj +++ b/Console/TeslaConsole.csproj @@ -41,6 +41,12 @@ + + + diff --git a/Console/TeslaConsole/ConsoleSettings.cs b/Console/TeslaConsole/ConsoleSettings.cs new file mode 100644 index 0000000..7138d99 --- /dev/null +++ b/Console/TeslaConsole/ConsoleSettings.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.Windows.Forms; +using System.Xml; + +namespace TeslaConsole; + +/// +/// Machine-level console settings that belong to no single game — the Settings +/// menu toggles that used to live only in memory and reset on every restart. +/// Stored as XML in %ProgramData%\Tesla Console\console.settings, next to +/// RPDefaults.rpd / BTDefaults.btd / local.siteconfig. +/// +/// The values themselves stay where they are used (PlasmaBitmaps owns +/// EnableCustomBitmaps); this class only moves them to and from disk, so the +/// original decompiled classes keep their shape. Load() is called once from +/// Program.Main and never from a static initializer: the differential test suite +/// drives PlasmaBitmaps directly, and must keep seeing the original defaults +/// rather than whatever this operator happens to have saved. +/// +internal static class ConsoleSettings +{ + private static readonly string sSettingsFilePath = Path.Combine(Program.GetCommonAppDataDirectory(), "console.settings"); + + /// + /// Applies the saved settings. A missing file is the normal first-run case; + /// a corrupt one is ignored and rewritten by the next Save(), because losing + /// a menu toggle must never stop the console from starting. + /// + public static void Load() + { + try + { + if (!File.Exists(sSettingsFilePath)) + { + return; + } + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.Load(sSettingsFilePath); + foreach (XmlNode childNode in xmlDocument.DocumentElement.ChildNodes) + { + switch (childNode.Name) + { + case "EnableCustomBitmaps": + { + if (bool.TryParse(childNode.InnerText, out var result)) + { + PlasmaBitmaps.EnableCustomBitmaps = result; + } + break; + } + } + } + } + catch (Exception) + { + } + } + + public static void Save() + { + try + { + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.AppendChild(xmlDocument.CreateElement("ConsoleSettings")); + xmlDocument.DocumentElement.AppendChild(xmlDocument.CreateElement("EnableCustomBitmaps")).InnerText = PlasmaBitmaps.EnableCustomBitmaps.ToString(); + string directoryName = Path.GetDirectoryName(sSettingsFilePath); + if (!Directory.Exists(directoryName)) + { + Directory.CreateDirectory(directoryName); + } + xmlDocument.Save(sSettingsFilePath); + } + catch (Exception) + { + MessageBox.Show("The console settings file could not be saved. This setting will only be remembered until the application is closed.", "Error Saving Console Settings!", MessageBoxButtons.OK); + } + } +} diff --git a/Console/TeslaConsole/PlasmaBitmaps.cs b/Console/TeslaConsole/PlasmaBitmaps.cs index 0b00b1a..1057279 100644 --- a/Console/TeslaConsole/PlasmaBitmaps.cs +++ b/Console/TeslaConsole/PlasmaBitmaps.cs @@ -4,6 +4,7 @@ using System.Drawing.Drawing2D; using System.Drawing.Text; using System.IO; using System.Text; +using System.Windows.Forms; namespace TeslaConsole; @@ -45,21 +46,10 @@ public class PlasmaBitmaps { if (sEnableCustomBitmaps) { - string text = Path.Combine(Program.GetCommonAppDataDirectory(), $"Plasma Images\\{str}_{width}x{height}.bmp"); - if (File.Exists(text)) + Bitmap bitmap = LoadCustomBitmap(width, height, str); + if (bitmap != null) { - try - { - Bitmap bitmap = (Bitmap)Image.FromFile(text); - if (bitmap.Width == width && bitmap.Height == height) - { - return bitmap; - } - bitmap.Dispose(); - } - catch - { - } + return bitmap; } } Bitmap bitmap2 = new Bitmap(width, height); @@ -87,6 +77,59 @@ public class PlasmaBitmaps } } + /// + /// The operator-supplied plasma image for this string at this size, or null + /// when there is none (the caller then renders the text procedurally). + /// + /// Two folders are searched, in order: + /// 1. %ProgramData%\Tesla Console\Plasma Images - this machine's own art, + /// which survives reinstalls and wins over anything shipped. + /// 2. Plasma Images\ next to TeslaConsole.exe - the set that rolls with + /// the release, so art can be version-controlled and deployed. + /// The file name is "<text>_<width>x<height>.bmp", e.g. Camera_128x32.bmp. + /// + /// Nothing here may throw: participant names come from operator input, and a + /// name containing a character that is illegal in a path used to take out + /// egg generation entirely once this option was switched on. + /// + private static Bitmap LoadCustomBitmap(int width, int height, string str) + { + string fileName = $"{str}_{width}x{height}.bmp"; + if (fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + { + return null; + } + string[] searchRoots = new string[2] + { + Program.GetCommonAppDataDirectory(), + Path.GetDirectoryName(Application.ExecutablePath) + }; + foreach (string searchRoot in searchRoots) + { + try + { + string path = Path.Combine(Path.Combine(searchRoot, "Plasma Images"), fileName); + if (!File.Exists(path)) + { + continue; + } + // Copied out of the file rather than Image.FromFile'd: that keeps the + // bitmap backed by the file for its whole lifetime, which locks the art + // against an operator swapping it while the console is running. + using FileStream stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); + using Image image = Image.FromStream(stream); + if (image.Width == width && image.Height == height) + { + return new Bitmap(image); + } + } + catch + { + } + } + return null; + } + public static void GenerateStrings(out string large, out string small, string str) { GenerateStrings(out large, out small, "Microsoft Sans Serif", str); diff --git a/Console/TeslaConsole/Program.cs b/Console/TeslaConsole/Program.cs index 6288286..86e6eb6 100644 --- a/Console/TeslaConsole/Program.cs +++ b/Console/TeslaConsole/Program.cs @@ -59,6 +59,8 @@ internal static class Program } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(defaultValue: false); + // Machine-level Settings-menu toggles, before the form reads them to set its check marks. + ConsoleSettings.Load(); Application.Run(new TeslaConsoleForm()); } diff --git a/Console/TeslaConsole/TeslaConsoleForm.cs b/Console/TeslaConsole/TeslaConsoleForm.cs index 701f4b7..b22734c 100644 --- a/Console/TeslaConsole/TeslaConsoleForm.cs +++ b/Console/TeslaConsole/TeslaConsoleForm.cs @@ -478,6 +478,7 @@ public class TeslaConsoleForm : Form private void enableCustomBitmapsToolStripMenuItem_Click(object sender, EventArgs e) { enableCustomBitmapsToolStripMenuItem.Checked = (PlasmaBitmaps.EnableCustomBitmaps = !PlasmaBitmaps.EnableCustomBitmaps); + ConsoleSettings.Save(); } private void mThrowHandledExceptionMenuItem_Click(object sender, EventArgs e) diff --git a/Console/install.bat b/Console/install.bat index 52cc8d4..5897b6a 100644 --- a/Console/install.bat +++ b/Console/install.bat @@ -64,8 +64,13 @@ echo Files copied. :: -- STEP 2: Data directory (site config, mission recordings) ----------------- echo [2/4] Preparing data directory... if not exist "%DATA_DIR%" mkdir "%DATA_DIR%" +:: Per-site plasma-display art. Created empty and never overwritten: what is in +:: here wins over the copy shipped in App\Plasma Images, so a release cannot +:: clobber a site's own name bitmaps. +if not exist "%DATA_DIR%\Plasma Images" mkdir "%DATA_DIR%\Plasma Images" :: Grant the local Users group modify access so a normal operator account can -:: write local.siteconfig / RP Missions when the console is not run elevated. +:: write local.siteconfig / console.settings / RP Missions when the console is +:: not run elevated. icacls "%DATA_DIR%" /grant *S-1-5-32-545:(OI)(CI)M /T >nul 2>&1 echo %DATA_DIR% (Users: modify access) diff --git a/Console/tests/TeslaConsole.DiffTests/BehavioralEquivalenceTests.cs b/Console/tests/TeslaConsole.DiffTests/BehavioralEquivalenceTests.cs index 7ff7bb5..4397d36 100644 --- a/Console/tests/TeslaConsole.DiffTests/BehavioralEquivalenceTests.cs +++ b/Console/tests/TeslaConsole.DiffTests/BehavioralEquivalenceTests.cs @@ -47,7 +47,7 @@ namespace TeslaConsole.DiffTests Assert.Contains("TeslaConsole", _fx.Original.AssemblyFullName); Assert.Contains("TeslaConsole", _fx.Recovered.AssemblyFullName); Assert.Contains("4.11.3.37076", _fx.Original.AssemblyFullName); - Assert.Contains("4.11.4.4", _fx.Recovered.AssemblyFullName); + Assert.Contains("4.11.4.5", _fx.Recovered.AssemblyFullName); } // ---- RPStrings.GetTimeString: mm:ss formatting with 0.5s rounding ---- diff --git a/Launcher/TeslaLauncher.csproj b/Launcher/TeslaLauncher.csproj index bfd8e29..3beca04 100644 --- a/Launcher/TeslaLauncher.csproj +++ b/Launcher/TeslaLauncher.csproj @@ -18,7 +18,7 @@ disable disable latest - 4.11.4.4 + 4.11.4.5 app.ico TeslaLauncher Tesla.Launcher diff --git a/Launcher/build.bat b/Launcher/build.bat index c8b25ac..e67f683 100644 --- a/Launcher/build.bat +++ b/Launcher/build.bat @@ -46,7 +46,7 @@ for %%a in (%*) do ( echo. echo ============================================================ -echo Tesla Launcher v4.11.4.4 - Build ^& Package (net40 single binary) +echo Tesla Launcher v4.11.4.5 - Build ^& Package (net40 single binary) echo Output : %BUILD_DIR% echo ============================================================ echo. diff --git a/Launcher/install.bat b/Launcher/install.bat index d4fc639..33ff2c3 100644 --- a/Launcher/install.bat +++ b/Launcher/install.bat @@ -20,7 +20,7 @@ setlocal enabledelayedexpansion echo ============================================================ -echo Tesla Launcher v4.11.4.4 - Installation (single binary) +echo Tesla Launcher v4.11.4.5 - Installation (single binary) echo ============================================================ echo. diff --git a/README.md b/README.md index 735b180..20e1229 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ console's Install Product (or run directly on any machine). Release packages for all three are attached to the [Gitea releases](https://gitea.mysticmachines.com/VWE/TeslaSuite/releases) -(latest: **v4.11.4.3**). +(latest: **v4.11.4.5**). ## Layout notes diff --git a/vPOD/vPOD.csproj b/vPOD/vPOD.csproj index f14d0bd..06eed6c 100644 --- a/vPOD/vPOD.csproj +++ b/vPOD/vPOD.csproj @@ -27,8 +27,8 @@ VPod true - 4.11.4.4 - 4.11.4.4 + 4.11.4.5 + 4.11.4.5 vPOD