From 270abfc5ad9c968c03c04e0e8a446f97d075017b Mon Sep 17 00:00:00 2001 From: Cyd Date: Tue, 30 Jun 2026 13:11:25 -0500 Subject: [PATCH] Docs: reflect net48; convert tools projects to net48 Documentation now describes the .NET Framework 4.8 stack (was .NET 8): - README build prerequisites + framework-dependent/no-runtime-install note, test count 136 -> 241. - PLAN.md architecture diagram, stack decision (with PolySharp/shims note), suite total 136 -> 241. - deploy/README-DEPLOY.txt: app is net48 framework-dependent (4.8 is in-box on Win10/11), and build prerequisites. Also migrate the three tools (RioJoySmokeTest, RioSerialMonitor, XcfRegionExtract) to net48 so they keep building against the now-net48 RioJoy.Core (a net8 project cannot reference a net48 one). Added PolySharp + System.Memory/System.Text.Json shims and replaced net-core-only APIs (string.Contains/IndexOf with comparison, Array.Fill, generic Enum.IsDefined, int.TryParse(span)). All three build clean. Co-Authored-By: Claude Opus 4.8 --- README.md | 9 ++++++--- deploy/README-DEPLOY.txt | 8 +++++--- docs/PLAN.md | 10 +++++++--- tools/RioJoySmokeTest/Program.cs | 2 +- tools/RioJoySmokeTest/RioJoySmokeTest.csproj | 9 ++++++++- tools/RioSerialMonitor/Program.cs | 4 ++-- tools/RioSerialMonitor/RioSerialMonitor.csproj | 13 ++++++++++++- tools/XcfRegionExtract/Program.cs | 2 +- tools/XcfRegionExtract/XcfRegionExtract.csproj | 10 +++++++++- 9 files changed, 51 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 3cb9fad..c11a700 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,11 @@ Red Planet — talk to the RIO directly and do not use this app.) ## Building -Requires the **.NET 8 SDK** and Windows. The driver builds separately with the -**WDK** (see [`driver/README.md`](driver/README.md)). +Requires the **.NET SDK** (8.0 or newer) plus the **.NET Framework 4.8 +targeting/developer pack**, on Windows. The apps target **.NET Framework 4.8**, +which is in-box on every Windows 10/11 machine — so deployed builds are +framework-dependent and need **no runtime install** on the target. The driver +builds separately with the **WDK** (see [`driver/README.md`](driver/README.md)). ```sh dotnet build RioJoy.sln -c Release @@ -37,7 +40,7 @@ dotnet test RioJoy.sln ## Status -Phases 1–5 are implemented and tested (136 unit tests). The `RioGamepad` virtual +Phases 1–5 are implemented and tested (241 unit tests). The `RioGamepad` virtual HID driver is built (KMDF + VHF), **test-signed, installed, and verified**: it enumerates in `joy.cpl`, and the C# HID feeder (`DeviceIoControl` → `RioGamepad.sys`) drives its axes, buttons, and hat end-to-end (see diff --git a/deploy/README-DEPLOY.txt b/deploy/README-DEPLOY.txt index b2849c8..8fa9bc1 100644 --- a/deploy/README-DEPLOY.txt +++ b/deploy/README-DEPLOY.txt @@ -11,8 +11,9 @@ Contents postinstall.bat Entry point (at the root). Elevates, then runs RIOJoy\install-rio.ps1. RIOJoy\ The payload folder: - app\ The RIOJoy tray application (self-contained .NET 8 — no runtime - install required on the target machine). + app\ The RIOJoy tray application (.NET Framework 4.8, framework- + dependent). .NET Framework 4.8 is in-box on Windows 10/11, so + no runtime install is required on the target machine. vendor\ The signed ViGEmBus installer (Nefarius, WHQL/attestation-signed). install-rio.ps1 The actual install steps (idempotent). pre-uninstall.bat Cleanup entry point. Elevates, then runs uninstall-rio.ps1. @@ -57,7 +58,8 @@ After install Building this package (on a dev machine) ----------------------------------------- - Requires the .NET 8 SDK and the signed ViGEmBus installer. + Requires the .NET SDK (8.0+) with the .NET Framework 4.8 targeting/developer + pack, and the signed ViGEmBus installer. powershell -ExecutionPolicy Bypass -File deploy\build-package.ps1 ` -VigemInstaller diff --git a/docs/PLAN.md b/docs/PLAN.md index fde7b8e..d2e47cb 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -25,7 +25,7 @@ cockpit's outputs on their behalf. ## Target architecture ``` - ┌─────────────────────── C# / .NET 8 tray app (x64) ───────────────────────┐ + ┌────────────────── C# / .NET Framework 4.8 tray app (x64) ─────────────────┐ │ Serial (RIO protocol) → Input mapper (profile) → Output router │ │ COM port, 9600 8N1 72 inputs + keypads ├─ Keyboard/Mouse: SendInput (P/Invoke) │ packet parse + ACK/NAK decode iRIO bitfield ├─ Joystick: HID report → DeviceIoControl ↓ @@ -46,7 +46,11 @@ cockpit's outputs on their behalf. - **Virtual joystick:** custom **VHF/UMDF HID driver** (full fidelity: 6 axes, 96 buttons, 1 hat — exactly the legacy vJoy layout). Not ViGEm (can't hold 96 buttons). -- **Stack:** C# / **.NET 8** (LTS), x64. Driver is C (WDK), separate toolchain. +- **Stack:** C# / **.NET Framework 4.8**, x64 — in-box on Windows 10/11, so + deployed builds are framework-dependent with no runtime to install. (Modern C# + language features that net48 lacks are supplied by the PolySharp source + generator + a few NuGet shims; see `src/RioJoy.Core/Compat/`.) Driver is C + (WDK), separate toolchain. - **Form:** background **tray app** (NotifyIcon), auto-start with Windows. - **Targets:** Windows 10/11 **x64 only**. The legacy x86 / WinXP targets are dropped. @@ -183,7 +187,7 @@ Implemented in `src/RioJoy.Core/Calibration` + `Plasma` (105 xUnit tests total): ### Phase 5 — Tray app + profiles — code-complete ✅ Core logic in `src/RioJoy.Core/Profiles` + `RioRuntime`; UI/OS in `src/RioJoy.Tray` -(136 xUnit tests total across the suite): +(241 xUnit tests total across the suite): - `RioProfile` + `AppConfig` model; `ConfigStore` JSON persistence (round-trip tested); `RioIniImporter` ports the legacy `RIO.ini` (buttons/inverts/greeting). - `AutoSwitchResolver` + `AutoSwitchWatcher`: the three-state decision diff --git a/tools/RioJoySmokeTest/Program.cs b/tools/RioJoySmokeTest/Program.cs index 9b66137..2a092dc 100644 --- a/tools/RioJoySmokeTest/Program.cs +++ b/tools/RioJoySmokeTest/Program.cs @@ -105,7 +105,7 @@ int FindJoyId() if (firstPresent < 0) { firstPresent = id; lastName = name; } // RioGamepad identity (Public.h: RIO_VENDOR_ID 0x1209 / RIO_PRODUCT_ID 0x5249). if (caps.wMid == 0x1209 && caps.wPid == 0x5249) { lastName = name; return id; } - if (name.Contains("RIOJoy", StringComparison.OrdinalIgnoreCase)) { lastName = name; return id; } + if (name.IndexOf("RIOJoy", StringComparison.OrdinalIgnoreCase) >= 0) { lastName = name; return id; } } return firstPresent; } diff --git a/tools/RioJoySmokeTest/RioJoySmokeTest.csproj b/tools/RioJoySmokeTest/RioJoySmokeTest.csproj index dc791e8..e60d84d 100644 --- a/tools/RioJoySmokeTest/RioJoySmokeTest.csproj +++ b/tools/RioJoySmokeTest/RioJoySmokeTest.csproj @@ -1,9 +1,10 @@ Exe - net8.0-windows + net48 enable enable + latest x64 x64 RioJoySmokeTest @@ -12,4 +13,10 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/tools/RioSerialMonitor/Program.cs b/tools/RioSerialMonitor/Program.cs index 50cb3b8..41c3200 100644 --- a/tools/RioSerialMonitor/Program.cs +++ b/tools/RioSerialMonitor/Program.cs @@ -52,7 +52,7 @@ AnalogReport lastLogged = default; bool haveAnalog = false; TimeSpan lastAnalogLog = TimeSpan.Zero; var min = new short[5]; var max = new short[5]; -Array.Fill(min, short.MaxValue); Array.Fill(max, short.MinValue); +for (int i = 0; i < min.Length; i++) { min[i] = short.MaxValue; max[i] = short.MinValue; } void Track(AnalogReport r) { @@ -113,7 +113,7 @@ link.AnalogReceived += r => link.VersionReceived += v => { version = v; Log($"VERSION reply: firmware {v}"); }; link.CheckReceived += c => { checks.Add(c); Log($"CHECK reply: {c}"); }; -link.ControlReceived += b => { controls++; Log($"control byte 0x{b:X2} ({(Enum.IsDefined((RioControl)b) ? (RioControl)b : (object)"?")})"); }; +link.ControlReceived += b => { controls++; Log($"control byte 0x{b:X2} ({(Enum.IsDefined(typeof(RioControl), (RioControl)b) ? (RioControl)b : (object)"?")})"); }; link.FramingError += () => { framing++; Log("FRAMING ERROR (resync)"); }; using var cts = new CancellationTokenSource(); diff --git a/tools/RioSerialMonitor/RioSerialMonitor.csproj b/tools/RioSerialMonitor/RioSerialMonitor.csproj index 70d5b50..9a7aabb 100644 --- a/tools/RioSerialMonitor/RioSerialMonitor.csproj +++ b/tools/RioSerialMonitor/RioSerialMonitor.csproj @@ -2,9 +2,12 @@ Exe - net8.0-windows + net48 enable enable + latest + x64 + x64 RioSerialMonitor @@ -12,4 +15,12 @@ + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/tools/XcfRegionExtract/Program.cs b/tools/XcfRegionExtract/Program.cs index 26b84c3..92204b4 100644 --- a/tools/XcfRegionExtract/Program.cs +++ b/tools/XcfRegionExtract/Program.cs @@ -30,7 +30,7 @@ ulong U64() { ulong v = 0; for (int i = 0; i < 8; i++) v = (v << 8) | d[pos++]; string magic = Encoding.ASCII.GetString(d, 0, 9); // "gimp xcf " string ver = Encoding.ASCII.GetString(d, 9, 4); // "file" (=v0) or "vNNN" if (magic != "gimp xcf ") { Console.Error.WriteLine($"Not an XCF file: '{magic}'"); return 1; } -bool ptr64 = ver[0] == 'v' && int.TryParse(ver.AsSpan(1, 3), out int vn) && vn >= 11; +bool ptr64 = ver[0] == 'v' && int.TryParse(ver.Substring(1, 3), out int vn) && vn >= 11; long Ptr() => ptr64 ? (long)U64() : U32(); pos = 14; diff --git a/tools/XcfRegionExtract/XcfRegionExtract.csproj b/tools/XcfRegionExtract/XcfRegionExtract.csproj index 3553741..1e32013 100644 --- a/tools/XcfRegionExtract/XcfRegionExtract.csproj +++ b/tools/XcfRegionExtract/XcfRegionExtract.csproj @@ -1,10 +1,18 @@ Exe - net8.0 + net48 enable enable + latest XcfRegionExtract + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + +