Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85595b8c52 | ||
|
|
106fa610c0 | ||
|
|
8ba428b6a4 |
@@ -1516,7 +1516,11 @@ internal class SitePanel : DockContent
|
|||||||
for (int i = 0; i < rVolumeItems.Length; i++)
|
for (int i = 0; i < rVolumeItems.Length; i++)
|
||||||
{
|
{
|
||||||
mnuVolume.DropDownItems.Add(rVolumeItems[i]);
|
mnuVolume.DropDownItems.Add(rVolumeItems[i]);
|
||||||
((ToolStripMenuItem)mnuVolume.DropDownItems[i]).Checked = i + 1 == num / 10;
|
// Item i displays i*10 ("mute" at 0), so the reported level maps to
|
||||||
|
// index num/10 directly. The original console checked i+1 here — the
|
||||||
|
// mark sat one step below the actual volume, and mute (0) never got
|
||||||
|
// a mark at all. Original bug (4.11.3), fixed 2026-07-11.
|
||||||
|
((ToolStripMenuItem)mnuVolume.DropDownItems[i]).Checked = i == num / 10;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-111
@@ -938,10 +938,8 @@ namespace Tesla.Launcher
|
|||||||
// ── Volume control ────────────────────────────────────────────────────
|
// ── Volume control ────────────────────────────────────────────────────
|
||||||
// The Console stores/retrieves volume as a float (0.0–1.0 scalar). We
|
// The Console stores/retrieves volume as a float (0.0–1.0 scalar). We
|
||||||
// cache the exact value the Console sent so get returns the same value
|
// cache the exact value the Console sent so get returns the same value
|
||||||
// without device roundtrip quantization error.
|
// without device roundtrip quantization error. The device chain
|
||||||
//
|
// (nircmd → CoreAudio → winmm) lives in VolumeControl.cs, shared with vPOD.
|
||||||
// Setter chain: nircmd.exe (legacy, works everywhere incl. XP)
|
|
||||||
// → CoreAudio (Vista+) → winmm waveOutSetVolume (XP fallback).
|
|
||||||
|
|
||||||
private float _cachedVolumeScalar = 1.0f;
|
private float _cachedVolumeScalar = 1.0f;
|
||||||
|
|
||||||
@@ -953,26 +951,7 @@ namespace Tesla.Launcher
|
|||||||
private void SetMasterVolume(float scalar)
|
private void SetMasterVolume(float scalar)
|
||||||
{
|
{
|
||||||
_cachedVolumeScalar = scalar;
|
_cachedVolumeScalar = scalar;
|
||||||
|
VolumeControl.SetMasterScalar(scalar, GAMES_DIR);
|
||||||
var nircmd = Path.Combine(GAMES_DIR, "nircmd.exe");
|
|
||||||
if (File.Exists(nircmd))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var p = Process.Start(nircmd, "setsysvolume " + (int)(scalar * 65535));
|
|
||||||
if (p != null) p.Dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch { /* fall through to API */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Environment.OSVersion.Version.Major >= 6)
|
|
||||||
{
|
|
||||||
try { CoreAudio.SetMasterScalar(scalar); return; }
|
|
||||||
catch { /* fall through */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
WinMmVolume.SetMasterScalar(scalar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Session key ───────────────────────────────────────────────────────
|
// ── Session key ───────────────────────────────────────────────────────
|
||||||
@@ -1201,91 +1180,5 @@ namespace Tesla.Launcher
|
|||||||
public bool AutoRestart { get; set; }
|
public bool AutoRestart { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Windows Core Audio API — minimal COM interop (Vista+) ─────────────────
|
// (Core Audio / winmm volume interop moved to VolumeControl.cs — shared with vPOD.)
|
||||||
// No external dependencies. Vtable slot order matches the SDK headers exactly.
|
|
||||||
// Methods we don't call are declared as void stubs to preserve vtable offsets.
|
|
||||||
// NOT available on XP — callers must gate on OS version.
|
|
||||||
|
|
||||||
internal static class CoreAudio
|
|
||||||
{
|
|
||||||
internal static void SetMasterScalar(float scalar)
|
|
||||||
{
|
|
||||||
var ep = GetEndpointVolume();
|
|
||||||
try { var ctx = Guid.Empty; ep.SetMasterVolumeLevelScalar(scalar, ref ctx); }
|
|
||||||
finally { Marshal.ReleaseComObject(ep); }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IAudioEndpointVolume GetEndpointVolume()
|
|
||||||
{
|
|
||||||
var enumerator = (IMMDeviceEnumerator)new MMAudioEnumeratorComClass();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IMMDevice device;
|
|
||||||
enumerator.GetDefaultAudioEndpoint(0 /*eRender*/, 1 /*eMultimedia*/, out device);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var iid = typeof(IAudioEndpointVolume).GUID;
|
|
||||||
object obj;
|
|
||||||
device.Activate(ref iid, 23 /*CLSCTX_ALL*/, IntPtr.Zero, out obj);
|
|
||||||
return (IAudioEndpointVolume)obj;
|
|
||||||
}
|
|
||||||
finally { Marshal.ReleaseComObject(device); }
|
|
||||||
}
|
|
||||||
finally { Marshal.ReleaseComObject(enumerator); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
|
||||||
internal class MMAudioEnumeratorComClass { }
|
|
||||||
|
|
||||||
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"),
|
|
||||||
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
internal interface IMMDeviceEnumerator
|
|
||||||
{
|
|
||||||
void _unused_EnumAudioEndpoints(); // slot 0 — not used
|
|
||||||
[PreserveSig]
|
|
||||||
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Guid("D666063F-1587-4E43-81F1-B948E807363F"),
|
|
||||||
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
internal interface IMMDevice
|
|
||||||
{
|
|
||||||
[PreserveSig]
|
|
||||||
int Activate(ref Guid iid, int clsCtx, IntPtr pActivationParams,
|
|
||||||
[MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vtable order (after IUnknown): RegisterControlChangeNotify(0),
|
|
||||||
// UnregisterControlChangeNotify(1), GetChannelCount(2),
|
|
||||||
// SetMasterVolumeLevel(3), SetMasterVolumeLevelScalar(4),
|
|
||||||
// GetMasterVolumeLevel(5), GetMasterVolumeLevelScalar(6)
|
|
||||||
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"),
|
|
||||||
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
||||||
internal interface IAudioEndpointVolume
|
|
||||||
{
|
|
||||||
void _unused_RegisterControlChangeNotify(); // slot 0
|
|
||||||
void _unused_UnregisterControlChangeNotify(); // slot 1
|
|
||||||
void _unused_GetChannelCount(); // slot 2
|
|
||||||
[PreserveSig] int SetMasterVolumeLevel(float levelDB, ref Guid ctx);
|
|
||||||
[PreserveSig] int SetMasterVolumeLevelScalar(float level, ref Guid ctx);
|
|
||||||
[PreserveSig] int GetMasterVolumeLevel(out float levelDB);
|
|
||||||
[PreserveSig] int GetMasterVolumeLevelScalar(out float level);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── winmm wave-out volume (XP fallback) ───────────────────────────────────
|
|
||||||
// waveOutSetVolume with device -1 sets the wave mixer level of the default
|
|
||||||
// device: low 16 bits = left channel, high 16 bits = right channel.
|
|
||||||
|
|
||||||
internal static class WinMmVolume
|
|
||||||
{
|
|
||||||
[DllImport("winmm.dll")]
|
|
||||||
private static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);
|
|
||||||
|
|
||||||
internal static void SetMasterScalar(float scalar)
|
|
||||||
{
|
|
||||||
uint level = (uint)(Math.Max(0f, Math.Min(1f, scalar)) * 0xFFFF);
|
|
||||||
try { waveOutSetVolume(IntPtr.Zero, (level << 16) | level); } catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// =============================================================================
|
||||||
|
// TeslaLauncher — system master volume (shared with vPOD)
|
||||||
|
// =============================================================================
|
||||||
|
// The Console's set_VolumeLevel ends here. Setter chain:
|
||||||
|
// nircmd.exe (legacy, works everywhere incl. XP, if present in the games dir)
|
||||||
|
// → Core Audio (Vista+) → winmm waveOutSetVolume (XP fallback).
|
||||||
|
//
|
||||||
|
// Like MiniZip.cs, this file is compiled into BOTH the launcher and vPOD
|
||||||
|
// (linked source): vPOD's "Actually set system volume" mode applies the
|
||||||
|
// console's volume commands through the exact code the real pod runs.
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Tesla.Launcher
|
||||||
|
{
|
||||||
|
internal static class VolumeControl
|
||||||
|
{
|
||||||
|
/// <summary>Sets the system master volume to <paramref name="scalar"/>
|
||||||
|
/// (clamped to 0.0–1.0). <paramref name="nircmdDir"/> is probed for
|
||||||
|
/// nircmd.exe first (the legacy path used on the original pods); the
|
||||||
|
/// Windows APIs are the fallback. Never throws.</summary>
|
||||||
|
public static void SetMasterScalar(float scalar, string nircmdDir)
|
||||||
|
{
|
||||||
|
scalar = Math.Max(0f, Math.Min(1f, scalar));
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(nircmdDir))
|
||||||
|
{
|
||||||
|
var nircmd = Path.Combine(nircmdDir, "nircmd.exe");
|
||||||
|
if (File.Exists(nircmd))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var p = Process.Start(nircmd, "setsysvolume " + (int)(scalar * 65535));
|
||||||
|
if (p != null) p.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch { /* fall through to API */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Environment.OSVersion.Version.Major >= 6)
|
||||||
|
{
|
||||||
|
try { CoreAudio.SetMasterScalar(scalar); return; }
|
||||||
|
catch { /* fall through */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
WinMmVolume.SetMasterScalar(scalar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Windows Core Audio API — minimal COM interop (Vista+) ─────────────────
|
||||||
|
// No external dependencies. Vtable slot order matches the SDK headers exactly.
|
||||||
|
// Methods we don't call are declared as void stubs to preserve vtable offsets.
|
||||||
|
// NOT available on XP — callers must gate on OS version.
|
||||||
|
|
||||||
|
internal static class CoreAudio
|
||||||
|
{
|
||||||
|
internal static void SetMasterScalar(float scalar)
|
||||||
|
{
|
||||||
|
var ep = GetEndpointVolume();
|
||||||
|
try { var ctx = Guid.Empty; ep.SetMasterVolumeLevelScalar(scalar, ref ctx); }
|
||||||
|
finally { Marshal.ReleaseComObject(ep); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IAudioEndpointVolume GetEndpointVolume()
|
||||||
|
{
|
||||||
|
var enumerator = (IMMDeviceEnumerator)new MMAudioEnumeratorComClass();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IMMDevice device;
|
||||||
|
enumerator.GetDefaultAudioEndpoint(0 /*eRender*/, 1 /*eMultimedia*/, out device);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var iid = typeof(IAudioEndpointVolume).GUID;
|
||||||
|
object obj;
|
||||||
|
device.Activate(ref iid, 23 /*CLSCTX_ALL*/, IntPtr.Zero, out obj);
|
||||||
|
return (IAudioEndpointVolume)obj;
|
||||||
|
}
|
||||||
|
finally { Marshal.ReleaseComObject(device); }
|
||||||
|
}
|
||||||
|
finally { Marshal.ReleaseComObject(enumerator); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
||||||
|
internal class MMAudioEnumeratorComClass { }
|
||||||
|
|
||||||
|
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"),
|
||||||
|
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IMMDeviceEnumerator
|
||||||
|
{
|
||||||
|
void _unused_EnumAudioEndpoints(); // slot 0 — not used
|
||||||
|
[PreserveSig]
|
||||||
|
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Guid("D666063F-1587-4E43-81F1-B948E807363F"),
|
||||||
|
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IMMDevice
|
||||||
|
{
|
||||||
|
[PreserveSig]
|
||||||
|
int Activate(ref Guid iid, int clsCtx, IntPtr pActivationParams,
|
||||||
|
[MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vtable order (after IUnknown): RegisterControlChangeNotify(0),
|
||||||
|
// UnregisterControlChangeNotify(1), GetChannelCount(2),
|
||||||
|
// SetMasterVolumeLevel(3), SetMasterVolumeLevelScalar(4),
|
||||||
|
// GetMasterVolumeLevel(5), GetMasterVolumeLevelScalar(6)
|
||||||
|
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"),
|
||||||
|
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||||
|
internal interface IAudioEndpointVolume
|
||||||
|
{
|
||||||
|
void _unused_RegisterControlChangeNotify(); // slot 0
|
||||||
|
void _unused_UnregisterControlChangeNotify(); // slot 1
|
||||||
|
void _unused_GetChannelCount(); // slot 2
|
||||||
|
[PreserveSig] int SetMasterVolumeLevel(float levelDB, ref Guid ctx);
|
||||||
|
[PreserveSig] int SetMasterVolumeLevelScalar(float level, ref Guid ctx);
|
||||||
|
[PreserveSig] int GetMasterVolumeLevel(out float levelDB);
|
||||||
|
[PreserveSig] int GetMasterVolumeLevelScalar(out float level);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── winmm wave-out volume (XP fallback) ───────────────────────────────────
|
||||||
|
// waveOutSetVolume with device -1 sets the wave mixer level of the default
|
||||||
|
// device: low 16 bits = left channel, high 16 bits = right channel.
|
||||||
|
|
||||||
|
internal static class WinMmVolume
|
||||||
|
{
|
||||||
|
[DllImport("winmm.dll")]
|
||||||
|
private static extern int waveOutSetVolume(IntPtr hwo, uint dwVolume);
|
||||||
|
|
||||||
|
internal static void SetMasterScalar(float scalar)
|
||||||
|
{
|
||||||
|
uint level = (uint)(Math.Max(0f, Math.Min(1f, scalar)) * 0xFFFF);
|
||||||
|
try { waveOutSetVolume(IntPtr.Zero, (level << 16) | level); } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,14 +4,21 @@ The Tesla cockpit-pod software, in one repository:
|
|||||||
|
|
||||||
| Folder | What it is | Target |
|
| Folder | What it is | Target |
|
||||||
|--------|------------|--------|
|
|--------|------------|--------|
|
||||||
| [`Console/`](Console/) | **TeslaConsole** — the operator console (WinForms) that configures and drives the pods. A decompiled reconstruction of the original `TeslaConsole.exe` (now the modernized 4.11.4.x line), with a differential test suite pinning it to the original 4.11.3.37076 baseline. | .NET Framework 4.8 |
|
| [`Console/`](Console/) | **TeslaConsole** — the operator console (WinForms) that configures and drives the pods. A decompiled reconstruction of the original `TeslaConsole.exe` (now the modernized 4.11.4.x line), with a differential test suite pinning it to the original 4.11.3.37076 baseline. | .NET Framework 4.0 |
|
||||||
| [`Launcher/`](Launcher/) | **TeslaLauncher** — the pod-side Service (Session 0 RPC listener) + Agent (user-session app launcher). A clean rewrite of the original launcher. | .NET Framework 4.8 |
|
| [`Launcher/`](Launcher/) | **TeslaLauncher** — the pod-side launcher: ONE userland tray app (RPC listener + app launcher). A clean rewrite of the original; the old Service+Agent split (a Session 0 workaround) is gone. | .NET Framework 4.0 |
|
||||||
| [`Contract/`](Contract/) | **Tesla.Contract** — the shared Console↔Launcher RPC contract: wire types, the client, and the framed-JSON protocol. Emits assembly `TeslaConsoleLaunchLib`. | .NET Framework 4.8 |
|
| [`Contract/`](Contract/) | **Tesla.Contract** — the shared Console↔Launcher RPC contract: wire types, the client, and the framed-JSON protocol. Emits assembly `TeslaConsoleLaunchLib`. | .NET Framework 4.0 |
|
||||||
| [`SecureConfig/`](SecureConfig/) | **Tesla.SecureConfig** — the first-boot pod provisioning protocol (UDP beacons, OFB crypto, RSA key exchange). Emits assembly `TeslaSecureConfiguration`. | .NET Framework 4.8 |
|
| [`SecureConfig/`](SecureConfig/) | **Tesla.SecureConfig** — the first-boot pod provisioning protocol (UDP beacons, OFB crypto, RSA key exchange). Emits assembly `TeslaSecureConfiguration`. | .NET Framework 4.0 |
|
||||||
| [`vPOD/`](vPOD/) | **vPOD** — a virtual pod for testing the consoles without cockpit hardware: impersonates both the game client (Munga, TCP 1501) and the pod's TeslaLauncher service (provisioning + Site Management / Install Product on TCP 53290). | .NET Framework 4.8 |
|
| [`vPOD/`](vPOD/) | **vPOD** — a virtual pod for testing the consoles without cockpit hardware: impersonates both the game client (Munga, TCP 1501) and the pod's TeslaLauncher (provisioning + Site Management / Install Product on TCP 53290). | .NET Framework 4.0 |
|
||||||
|
|
||||||
The console and launcher talk over **TCP 53290** using **length-prefixed
|
Everything targets **net40** on purpose (the XP11 port, v4.11.4.3): it is the newest
|
||||||
`System.Text.Json` frames over an OFB-encrypted stream** ([`Contract/PodRpcProtocol.cs`](Contract/PodRpcProtocol.cs)),
|
.NET Framework that installs on Windows XP SP3, and net40 assemblies run in-place on
|
||||||
|
the 4.8 runtime that ships in Windows 10/11 — so the same binaries cover the original
|
||||||
|
XP-era cockpit PCs and modern hardware. That rules out net45+ APIs
|
||||||
|
(`System.Text.Json`, `ZipFile`, async/await, ...); JSON is Newtonsoft, zip extraction
|
||||||
|
is the launcher's own [`MiniZip.cs`](Launcher/MiniZip.cs).
|
||||||
|
|
||||||
|
The console and launcher talk over **TCP 53290** using **length-prefixed JSON
|
||||||
|
frames over an OFB-encrypted stream** ([`Contract/PodRpcProtocol.cs`](Contract/PodRpcProtocol.cs)),
|
||||||
dispatched by method name. The wire contract lives in one source project
|
dispatched by method name. The wire contract lives in one source project
|
||||||
([`Contract/`](Contract/)) referenced by both sides — a single source of truth, no
|
([`Contract/`](Contract/)) referenced by both sides — a single source of truth, no
|
||||||
duplication or hand-syncing.
|
duplication or hand-syncing.
|
||||||
@@ -28,15 +35,21 @@ dotnet test Console/tests/TeslaConsole.DiffTests # differential + protocol
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Pod deployment:** [`Launcher/build.bat`](Launcher/build.bat) publishes the
|
**Pod deployment:** [`Launcher/build.bat`](Launcher/build.bat) publishes the
|
||||||
framework-dependent net48 package into `Launcher/dist/` (~1.6 MB zipped — no runtime to
|
framework-dependent net40 package into `Launcher/dist/` — the launcher itself is tiny,
|
||||||
install, since .NET Framework 4.8 ships in Windows 10/11), and
|
but the package bundles the pod redists (DirectX June 2010, OpenAL, UltraVNC, and
|
||||||
[`Launcher/install.bat`](Launcher/install.bat) deploys it on a cockpit PC (registers the
|
`dotNetFx40_Full_x86_x64.exe` for XP-era pods that don't have .NET 4.0 yet).
|
||||||
Service, sets up the Agent for auto-login, hardens the box). The operator console packages
|
[`Launcher/install.bat`](Launcher/install.bat) deploys it on a cockpit PC — dual-OS
|
||||||
|
(XP SP3 and Win10/11 code paths): auto-login, Run-key registration for the single
|
||||||
|
launcher binary, firewall + box hardening. The operator console packages
|
||||||
the same way: [`Console/build-package.bat`](Console/build-package.bat) → `Console/dist/`,
|
the same way: [`Console/build-package.bat`](Console/build-package.bat) → `Console/dist/`,
|
||||||
installed with [`Console/install.bat`](Console/install.bat). vPOD packages with
|
installed with [`Console/install.bat`](Console/install.bat). vPOD packages with
|
||||||
[`vPOD/pack.ps1`](vPOD/pack.ps1) → `vPOD/dist/vPOD.zip`, deployable to a pod via the
|
[`vPOD/pack.ps1`](vPOD/pack.ps1) → `vPOD/dist/vPOD.zip`, deployable to a pod via the
|
||||||
console's Install Product (or run directly on any machine).
|
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**).
|
||||||
|
|
||||||
## Layout notes
|
## Layout notes
|
||||||
|
|
||||||
- `Console/original/TeslaConsole.exe` — the **4.11.3.37076** reference baseline the
|
- `Console/original/TeslaConsole.exe` — the **4.11.3.37076** reference baseline the
|
||||||
@@ -53,6 +66,11 @@ console's Install Product (or run directly on any machine).
|
|||||||
The system was modernized in 2026: the duplicated wire contract was extracted to a single
|
The system was modernized in 2026: the duplicated wire contract was extracted to a single
|
||||||
source project, `BinaryFormatter` (an RCE sink, and what pinned the launcher to an old
|
source project, `BinaryFormatter` (an RCE sink, and what pinned the launcher to an old
|
||||||
runtime) was replaced with the framed-JSON protocol, and the launcher was rebuilt — briefly
|
runtime) was replaced with the framed-JSON protocol, and the launcher was rebuilt — briefly
|
||||||
on net8/x64, then settled on net48 to match the console and ship a tiny, runtime-free
|
on net8/x64, then on net48, then (the **XP11** port, v4.11.4.3) the whole suite settled on
|
||||||
package. The whole console↔pod path (provisioning, install, launch) is validated on real
|
**net40** so one set of binaries runs on the original Windows XP SP3 cockpit hardware and
|
||||||
pods.
|
on Windows 10/11 alike. XP11 also merged the launcher's Service+Agent pair — a workaround
|
||||||
|
for Vista+ Session 0 isolation that XP never needed — back into a single userland app, and
|
||||||
|
moved the console's `.resx` BinaryFormatter bitmaps to raw embedded images (their runtime
|
||||||
|
reader doesn't exist on net40). The whole console↔pod path (provisioning, install, launch)
|
||||||
|
is validated on real pods; the net40 build is bench-validated on Win11's 4.8 runtime (real
|
||||||
|
XP SP3 hardware still pending).
|
||||||
|
|||||||
@@ -29,9 +29,10 @@ namespace VPod;
|
|||||||
/// - Install completion reports 99% (not 100) — the console's
|
/// - Install completion reports 99% (not 100) — the console's
|
||||||
/// InstallProductWorker breaks its retry loop only on 99.
|
/// InstallProductWorker breaks its retry loop only on 99.
|
||||||
///
|
///
|
||||||
/// All state lives in <see cref="VirtualLauncher" />; a packaged postinstall.bat
|
/// All state lives in <see cref="VirtualLauncher" />; packaged product scripts
|
||||||
/// is logged and removed unrun unless <see cref="VirtualLauncher.RunPostInstall" />
|
/// (postinstall.bat here, pre-uninstall.bat in UninstallApp) are logged and
|
||||||
/// is set from the vPOD window, in which case it is executed like the real service.
|
/// removed unrun unless <see cref="VirtualLauncher.RunPackageScripts" /> is set
|
||||||
|
/// from the vPOD window, in which case they run like on the real pod.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class LauncherRpcServer
|
internal sealed class LauncherRpcServer
|
||||||
{
|
{
|
||||||
@@ -350,13 +351,13 @@ internal sealed class LauncherRpcServer
|
|||||||
Log?.Invoke($"Install {callId:N}: extracted to {gamesRoot}");
|
Log?.Invoke($"Install {callId:N}: extracted to {gamesRoot}");
|
||||||
|
|
||||||
// The real service runs (then deletes) a packaged postinstall.bat here.
|
// The real service runs (then deletes) a packaged postinstall.bat here.
|
||||||
// vPOD only does so when the operator opts in via RunPostInstall;
|
// vPOD only does so when the operator opts in via RunPackageScripts;
|
||||||
// otherwise the script is logged and removed unrun (default), since it
|
// otherwise the script is logged and removed unrun (default), since it
|
||||||
// runs package code on the host machine.
|
// runs package code on the host machine.
|
||||||
string postInstall = Path.Combine(gamesRoot, "postinstall.bat");
|
string postInstall = Path.Combine(gamesRoot, "postinstall.bat");
|
||||||
if (File.Exists(postInstall))
|
if (File.Exists(postInstall))
|
||||||
{
|
{
|
||||||
if (mLauncher.RunPostInstall)
|
if (mLauncher.RunPackageScripts)
|
||||||
{
|
{
|
||||||
mLauncher.UpdateProgress(callId, 96, "Running postinstall...");
|
mLauncher.UpdateProgress(callId, 96, "Running postinstall...");
|
||||||
Log?.Invoke($"Install {callId:N}: running postinstall.bat...");
|
Log?.Invoke($"Install {callId:N}: running postinstall.bat...");
|
||||||
|
|||||||
+33
-12
@@ -55,9 +55,10 @@ internal sealed class VPodForm : Form
|
|||||||
private Label mInstallStatusLabel;
|
private Label mInstallStatusLabel;
|
||||||
private ProgressBar mInstallProgressBar;
|
private ProgressBar mInstallProgressBar;
|
||||||
private Button mReprovisionButton;
|
private Button mReprovisionButton;
|
||||||
private CheckBox mRunPostInstallCheckbox;
|
private CheckBox mRunScriptsCheckbox;
|
||||||
private CheckBox mRealLaunchCheckbox;
|
private CheckBox mRealLaunchCheckbox;
|
||||||
private CheckBox mRealAutoRestartCheckbox;
|
private CheckBox mRealAutoRestartCheckbox;
|
||||||
|
private CheckBox mRealVolumeCheckbox;
|
||||||
private ListView mAppsView;
|
private ListView mAppsView;
|
||||||
|
|
||||||
private bool mPoweredOn;
|
private bool mPoweredOn;
|
||||||
@@ -283,7 +284,7 @@ internal sealed class VPodForm : Form
|
|||||||
Padding = new Padding(8)
|
Padding = new Padding(8)
|
||||||
};
|
};
|
||||||
|
|
||||||
Panel siteTop = new Panel { Dock = DockStyle.Top, Height = 278 };
|
Panel siteTop = new Panel { Dock = DockStyle.Top, Height = 300 };
|
||||||
|
|
||||||
mProvisionStatusLabel = new Label
|
mProvisionStatusLabel = new Label
|
||||||
{
|
{
|
||||||
@@ -354,21 +355,22 @@ internal sealed class VPodForm : Form
|
|||||||
};
|
};
|
||||||
mReprovisionButton.Click += ReprovisionClicked;
|
mReprovisionButton.Click += ReprovisionClicked;
|
||||||
|
|
||||||
// Off (default) = a packaged postinstall.bat is logged and removed unrun;
|
// Off (default) = packaged product scripts (postinstall.bat on install,
|
||||||
// on = it is executed at the end of an install, like the real Agent.
|
// pre-uninstall.bat on uninstall) are logged and removed unrun; on =
|
||||||
mRunPostInstallCheckbox = new CheckBox
|
// they are executed like on the real pod.
|
||||||
|
mRunScriptsCheckbox = new CheckBox
|
||||||
{
|
{
|
||||||
Text = "Run postinstall.bat after install",
|
Text = "Run package install/uninstall scripts",
|
||||||
Location = new Point(4, 206),
|
Location = new Point(4, 206),
|
||||||
AutoSize = true,
|
AutoSize = true,
|
||||||
Checked = false
|
Checked = false
|
||||||
};
|
};
|
||||||
mRunPostInstallCheckbox.CheckedChanged += (s, e) =>
|
mRunScriptsCheckbox.CheckedChanged += (s, e) =>
|
||||||
{
|
{
|
||||||
mLauncher.RunPostInstall = mRunPostInstallCheckbox.Checked;
|
mLauncher.RunPackageScripts = mRunScriptsCheckbox.Checked;
|
||||||
OnSiteLog(mRunPostInstallCheckbox.Checked
|
OnSiteLog(mRunScriptsCheckbox.Checked
|
||||||
? "Installs now EXECUTE a packaged postinstall.bat."
|
? "Packaged postinstall.bat / pre-uninstall.bat now EXECUTE."
|
||||||
: "Installs skip (and remove) a packaged postinstall.bat.");
|
: "Packaged postinstall.bat / pre-uninstall.bat are skipped (and removed).");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Off = LaunchApp records simulated PIDs; on = start/kill real processes
|
// Off = LaunchApp records simulated PIDs; on = start/kill real processes
|
||||||
@@ -408,6 +410,24 @@ internal sealed class VPodForm : Form
|
|||||||
: "Watchdog OFF: exited apps stay down.");
|
: "Watchdog OFF: exited apps stay down.");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Off (default) = set_VolumeLevel only stores/echoes the float; on = it
|
||||||
|
// drives this machine's real master volume through the launcher's own
|
||||||
|
// chain (nircmd in the games root -> Core Audio -> winmm).
|
||||||
|
mRealVolumeCheckbox = new CheckBox
|
||||||
|
{
|
||||||
|
Text = "Actually set system volume",
|
||||||
|
Location = new Point(4, 272),
|
||||||
|
AutoSize = true,
|
||||||
|
Checked = false
|
||||||
|
};
|
||||||
|
mRealVolumeCheckbox.CheckedChanged += (s, e) =>
|
||||||
|
{
|
||||||
|
mLauncher.RealVolume = mRealVolumeCheckbox.Checked;
|
||||||
|
OnSiteLog(mRealVolumeCheckbox.Checked
|
||||||
|
? "set_VolumeLevel now changes the REAL system master volume."
|
||||||
|
: "set_VolumeLevel now only stores the value (no audio change).");
|
||||||
|
};
|
||||||
|
|
||||||
siteTop.Controls.Add(mProvisionStatusLabel);
|
siteTop.Controls.Add(mProvisionStatusLabel);
|
||||||
siteTop.Controls.Add(mPassphrasePanel);
|
siteTop.Controls.Add(mPassphrasePanel);
|
||||||
siteTop.Controls.Add(mNetConfigLabel);
|
siteTop.Controls.Add(mNetConfigLabel);
|
||||||
@@ -415,9 +435,10 @@ internal sealed class VPodForm : Form
|
|||||||
siteTop.Controls.Add(mInstallStatusLabel);
|
siteTop.Controls.Add(mInstallStatusLabel);
|
||||||
siteTop.Controls.Add(mInstallProgressBar);
|
siteTop.Controls.Add(mInstallProgressBar);
|
||||||
siteTop.Controls.Add(mReprovisionButton);
|
siteTop.Controls.Add(mReprovisionButton);
|
||||||
siteTop.Controls.Add(mRunPostInstallCheckbox);
|
siteTop.Controls.Add(mRunScriptsCheckbox);
|
||||||
siteTop.Controls.Add(mRealLaunchCheckbox);
|
siteTop.Controls.Add(mRealLaunchCheckbox);
|
||||||
siteTop.Controls.Add(mRealAutoRestartCheckbox);
|
siteTop.Controls.Add(mRealAutoRestartCheckbox);
|
||||||
|
siteTop.Controls.Add(mRealVolumeCheckbox);
|
||||||
|
|
||||||
mAppsView = new ListView
|
mAppsView = new ListView
|
||||||
{
|
{
|
||||||
|
|||||||
+72
-6
@@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
// No System.Text.Json on net40 — persistence goes through Newtonsoft, which
|
// No System.Text.Json on net40 — persistence goes through Newtonsoft, which
|
||||||
// writes the same JSON shape (public fields, PascalCase; see PodRpcProtocol).
|
// writes the same JSON shape (public fields, PascalCase; see PodRpcProtocol).
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Tesla.Launcher;
|
||||||
using Tesla.Net;
|
using Tesla.Net;
|
||||||
|
|
||||||
namespace VPod;
|
namespace VPod;
|
||||||
@@ -53,11 +54,20 @@ internal sealed class VirtualLauncher
|
|||||||
/// exactly like the real pod. Toggled from the vPOD window.</summary>
|
/// exactly like the real pod. Toggled from the vPOD window.</summary>
|
||||||
public bool RealAutoRestart { get; set; } = true;
|
public bool RealAutoRestart { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>When set, a packaged postinstall.bat is executed at the end of a
|
/// <summary>When set, packaged product scripts are executed like on the real
|
||||||
/// product install (like the real Agent) before being deleted, instead of
|
/// pod: postinstall.bat at the end of a product install (before being
|
||||||
/// being logged and removed unrun. Toggled from the vPOD window; off by
|
/// deleted), and pre-uninstall.bat before the product directory is removed
|
||||||
/// default, since it runs package script code on the host machine.</summary>
|
/// on uninstall. Off by default — both run package script code on the host
|
||||||
public bool RunPostInstall { get; set; }
|
/// machine — in which case they are logged and removed unrun. Toggled from
|
||||||
|
/// the vPOD window.</summary>
|
||||||
|
public bool RunPackageScripts { get; set; }
|
||||||
|
|
||||||
|
/// <summary>When set, the console's set_VolumeLevel changes this machine's
|
||||||
|
/// REAL master volume through the launcher's own chain (nircmd.exe in the
|
||||||
|
/// games root → Core Audio → winmm), exactly like the real pod. Off by
|
||||||
|
/// default: the value is only stored and displayed. Toggled from the vPOD
|
||||||
|
/// window.</summary>
|
||||||
|
public bool RealVolume { get; set; }
|
||||||
|
|
||||||
// Bumped to cancel watchdog restarts pending in their 2 s delay — the pod
|
// Bumped to cancel watchdog restarts pending in their 2 s delay — the pod
|
||||||
// "machine" went dark (power off / reboot / reprovision), so nothing may
|
// "machine" went dark (power off / reboot / reprovision), so nothing may
|
||||||
@@ -140,7 +150,17 @@ internal sealed class VirtualLauncher
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
lock (mLock) { mVolumeLevel = value; }
|
lock (mLock) { mVolumeLevel = value; }
|
||||||
Log?.Invoke($"Volume set to {value:P0}.");
|
if (RealVolume)
|
||||||
|
{
|
||||||
|
// The launcher's own device chain; probes GamesRoot for
|
||||||
|
// nircmd.exe first, like the real pod's GAMES_DIR.
|
||||||
|
VolumeControl.SetMasterScalar(value, GamesRoot);
|
||||||
|
Log?.Invoke($"Volume set to {value:P0} (applied to system).");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log?.Invoke($"Volume set to {value:P0} (simulated).");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +255,7 @@ internal sealed class VirtualLauncher
|
|||||||
{
|
{
|
||||||
if (Directory.Exists(cleanupDir))
|
if (Directory.Exists(cleanupDir))
|
||||||
{
|
{
|
||||||
|
RunPreUninstallScript(cleanupDir);
|
||||||
Directory.Delete(cleanupDir, recursive: true);
|
Directory.Delete(cleanupDir, recursive: true);
|
||||||
Log?.Invoke($"UninstallApp: removed product directory {cleanupDir}");
|
Log?.Invoke($"UninstallApp: removed product directory {cleanupDir}");
|
||||||
}
|
}
|
||||||
@@ -248,6 +269,51 @@ internal sealed class VirtualLauncher
|
|||||||
AppsChanged?.Invoke();
|
AppsChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>The real Agent's pre-uninstall hook: a product may ship a
|
||||||
|
/// pre-uninstall.bat (e.g. RIOJoy removes its driver) that runs before its
|
||||||
|
/// directory is deleted. Executed only when <see cref="RunPackageScripts" />
|
||||||
|
/// is opted in — otherwise logged and removed unrun (it dies with the
|
||||||
|
/// directory). Mirrors the launcher's CleanupProductDirectory.</summary>
|
||||||
|
private void RunPreUninstallScript(string productDir)
|
||||||
|
{
|
||||||
|
string preUninstall = Path.Combine(productDir, "pre-uninstall.bat");
|
||||||
|
if (!File.Exists(preUninstall))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!RunPackageScripts)
|
||||||
|
{
|
||||||
|
Log?.Invoke("UninstallApp: pre-uninstall.bat present — NOT executed (vPOD), removed with the directory.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log?.Invoke("UninstallApp: running pre-uninstall.bat...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ProcessStartInfo psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = preUninstall,
|
||||||
|
WorkingDirectory = productDir,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
using (Process proc = Process.Start(psi))
|
||||||
|
{
|
||||||
|
if (proc != null && proc.WaitForExit(120000))
|
||||||
|
{
|
||||||
|
Log?.Invoke($"UninstallApp: pre-uninstall.bat exited with code {proc.ExitCode}.");
|
||||||
|
}
|
||||||
|
else if (proc != null)
|
||||||
|
{
|
||||||
|
Log?.Invoke("UninstallApp: pre-uninstall.bat still running after 2 min — continuing.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log?.Invoke($"UninstallApp: pre-uninstall.bat failed to run: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void RemoveApp(Guid launchKey)
|
public void RemoveApp(Guid launchKey)
|
||||||
{
|
{
|
||||||
lock (mLock)
|
lock (mLock)
|
||||||
|
|||||||
@@ -58,6 +58,9 @@
|
|||||||
behavior to the real pod service, and the differential suite's install
|
behavior to the real pod service, and the differential suite's install
|
||||||
round-trip exercises MiniZip against real ZipArchive-built archives. -->
|
round-trip exercises MiniZip against real ZipArchive-built archives. -->
|
||||||
<Compile Include="..\Launcher\MiniZip.cs" Link="MiniZip.cs" />
|
<Compile Include="..\Launcher\MiniZip.cs" Link="MiniZip.cs" />
|
||||||
|
<!-- The real pod's master-volume chain (nircmd -> CoreAudio -> winmm), for
|
||||||
|
the "Actually set system volume" mode. Same linked-source sharing. -->
|
||||||
|
<Compile Include="..\Launcher\VolumeControl.cs" Link="VolumeControl.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user