Network-agnostic, air-gapped pod install for the two DOSBox titles (BT/RP 4.10), fitting the existing TeslaConsole/TeslaLauncher pod-bay architecture. emulator/DEPLOYMENT-PLAN.md Full design: bridge on the one NIC (pods form a source-proven P2P TCP mesh, so NAT/slirp is out); launcher keeps the bay IP, the DOSBox guest bridges at bayIP+100 (egg/mesh/game endpoint); two-edit +100 convention (postinstall + console DOSBox flag); static, air-gapped, <=32 pods. emulator/pod-launch/ (C# net8 supervising entry-point) Creates a Job Object (KILL_ON_JOB_CLOSE), launches DOSBox-X + the render bridge into it and blocks -- kill this process and both die (kernel-enforced, even on a hard TerminateProcess of a hung session; verified). Mode dispatch: bt/rp wired (also serve camera + live mission-review via egg hostType); review + diagnostics recognized but fail clean until their DOS launch args are known. emulator/deploy/ (install side) postinstall.bat (thin elevated wrapper) + configure.ps1 (NIC detect, realnic bind as a contiguous letter-leading GUID fragment, game IP = bayIP+100, stable MAC, render net_*.conf templates, stamp WATTCP.CFG my_ip) + tokenized conf templates + package.ps1 (assemble the zip). Render/bind/stamp + packaging verified against a scratch tree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
4.2 KiB
C#
110 lines
4.2 KiB
C#
// A Windows Job Object configured to KILL every process in it the instant the
|
|
// job's last handle closes. This process holds that handle for the whole
|
|
// session, so when it dies -- gracefully, on Ctrl-C, or on a hard
|
|
// TerminateProcess by TeslaLauncher -- DOSBox and the render bridge die too.
|
|
// Kernel-enforced; no cooperative shutdown required (the hung-DOSBox case).
|
|
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace VwePod
|
|
{
|
|
internal sealed class JobObject : IDisposable
|
|
{
|
|
private IntPtr _handle;
|
|
private bool _disposed;
|
|
|
|
public JobObject()
|
|
{
|
|
_handle = CreateJobObject(IntPtr.Zero, null);
|
|
if (_handle == IntPtr.Zero)
|
|
throw new InvalidOperationException("CreateJobObject failed: " + Marshal.GetLastWin32Error());
|
|
|
|
var ext = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
{
|
|
BasicLimitInformation = new JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
{
|
|
LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
|
|
}
|
|
};
|
|
|
|
int len = Marshal.SizeOf(ext);
|
|
IntPtr p = Marshal.AllocHGlobal(len);
|
|
try
|
|
{
|
|
Marshal.StructureToPtr(ext, p, false);
|
|
if (!SetInformationJobObject(_handle, JobObjectExtendedLimitInformation, p, (uint)len))
|
|
throw new InvalidOperationException("SetInformationJobObject failed: " + Marshal.GetLastWin32Error());
|
|
}
|
|
finally { Marshal.FreeHGlobal(p); }
|
|
}
|
|
|
|
// Assign a process to the job. Any process the job member later spawns is
|
|
// automatically in the job too, so a whole tree is captured.
|
|
public void Add(IntPtr processHandle)
|
|
{
|
|
if (!AssignProcessToJobObject(_handle, processHandle))
|
|
throw new InvalidOperationException("AssignProcessToJobObject failed: " + Marshal.GetLastWin32Error());
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
if (_handle != IntPtr.Zero)
|
|
{
|
|
CloseHandle(_handle); // last handle -> KILL_ON_JOB_CLOSE fires
|
|
_handle = IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
// ---- native ----
|
|
private const int JobObjectExtendedLimitInformation = 9;
|
|
private const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000;
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct JOBOBJECT_BASIC_LIMIT_INFORMATION
|
|
{
|
|
public long PerProcessUserTimeLimit;
|
|
public long PerJobUserTimeLimit;
|
|
public uint LimitFlags;
|
|
public UIntPtr MinimumWorkingSetSize;
|
|
public UIntPtr MaximumWorkingSetSize;
|
|
public uint ActiveProcessLimit;
|
|
public UIntPtr Affinity;
|
|
public uint PriorityClass;
|
|
public uint SchedulingClass;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct IO_COUNTERS
|
|
{
|
|
public ulong ReadOperationCount, WriteOperationCount, OtherOperationCount;
|
|
public ulong ReadTransferCount, WriteTransferCount, OtherTransferCount;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
|
|
{
|
|
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
|
public IO_COUNTERS IoInfo;
|
|
public UIntPtr ProcessMemoryLimit;
|
|
public UIntPtr JobMemoryLimit;
|
|
public UIntPtr PeakProcessMemoryUsed;
|
|
public UIntPtr PeakJobMemoryUsed;
|
|
}
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool SetInformationJobObject(IntPtr hJob, int infoClass, IntPtr lpInfo, uint cbInfoLength);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool CloseHandle(IntPtr hObject);
|
|
}
|
|
}
|