Phase 0: scaffold modern RIOJoy solution + plan

Modernization of the legacy vJoy-based RIO cockpit interface for Win10/11,
removing the vJoy dependency in favor of a custom VHF/UMDF HID driver,
rewritten in C#/.NET 8 as a background tray app with per-game profiles.

- Reorganize: legacy C++ -> legacy/, cockpit art -> docs/reference/
- RioJoy.sln: src/RioJoy.Core (lib) + src/RioJoy.Tray (tray app), net8.0-windows x64
- driver/ placeholder for the RioGamepad WDK driver
- docs/PLAN.md (7-phase plan; profiles + serial-yield model)
- docs/PROTOCOL.md (RIO wire format + iRIO input-map reference)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-06-26 12:43:01 -05:00
co-authored by Claude Opus 4.8
commit 39a3dab1fc
54 changed files with 13616 additions and 0 deletions
+168
View File
@@ -0,0 +1,168 @@
# RIOJoy — modernization plan
Modernize the cockpit RIO interface app for Windows 10/11, **removing the vJoy
dependency** and replacing it with a custom virtual HID device, rewritten in
C#/.NET as a background tray app with **per-game profiles**.
The legacy app is preserved under [`legacy/`](../legacy/) as the behavioral
reference. The RIO wire format and input map are documented in
[PROTOCOL.md](PROTOCOL.md).
---
## Purpose
The cockpits run two native games (**Firestorm**, **Red Planet**) that talk to
the RIO hardware **directly** and never use this app. RIOJoy exists to **broaden
which other games can run in the cockpits**: arbitrary games don't know about the
cockpit's extra hardware (5 analog axes, 96 lighted buttons, the plasma/VFD
display, the labeled wallpaper), so RIOJoy bridges the RIO to whatever input
those games *do* understand — joystick, keyboard, and mouse — and drives the
cockpit's outputs on their behalf.
---
## Target architecture
```
┌─────────────────────── C# / .NET 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 ↓
│ analog poll + recovery axis calibration └─ Lamps: LampRequest back over serial
│ Plasma/VFD on 2nd COM · Profiles + auto-switch + tray UI + logging │
└───────────────────────────────────────────────┬───────────────────────────┘
│ IOCTL (input report bytes)
┌─────────────────────────────────────────────────▼──────────────────────────┐
│ RioGamepad.sys — KMDF + VHF (vhf.sys) virtual HID device │
│ Report descriptor: X,Y,Z,Rx,Ry,Rz (16-bit) · 1 hat · 96 buttons │
│ Control device + custom IOCTL → VhfReadReportSubmit() → Windows sees │
│ a HID gamepad │
└─────────────────────────────────────────────────────────────────────────────┘
```
### Decisions (confirmed)
- **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.
- **Form:** background **tray app** (NotifyIcon), auto-start with Windows.
- **Targets:** Windows 10/11 **x64 only**. The legacy x86 / WinXP targets are
dropped.
### What ports over vs. what's new
| Concern | Legacy | Modern |
|---|---|---|
| Serial + RIO protocol | overlapped I/O + watch thread | `SerialPort` + async loop; faithful port of the packet state machine |
| Input decode | `iRIO[]` bitfield + `Press_V2` | same semantics, ported to C# |
| Keyboard/mouse | `SendInput` scancode | `SendInput` via P/Invoke (≈verbatim) |
| Joystick | vJoy `SetAxis/SetBtn/SetDiscPov` | **HID report → IOCTL → RioGamepad.sys** |
| Lamps | `LampRequest` over serial | same |
| Axis calibration | `UpdateJoystick/Throttle/Padal` | ported math |
| Plasma display | `CPlasma` (COM2) | ported; content per-profile |
| Config | SimpleIni, single file, hard-coded COM1 | profile library (JSON), configurable ports; importer for legacy `RIO.ini` |
---
## Profiles (the core abstraction)
A **profile** fully describes how the cockpit behaves for **one non-native
game**. Everything is per-profile, not global:
- Button/keypad mapping (the decoded `iRIO` table for this game)
- Axis calibration, curves, and invert flags
- Lamp behavior
- Plasma/VFD content (or "off")
- Cockpit overlay labels + the generated wallpaper (Phase 7)
- Display/resolution targets
Profiles **never** describe the native games — those are the "hands-off" case.
### Serial-port yield & the auto-switch state machine
Because the native games own the RIO's COM port directly, RIOJoy must yield it.
A process/window watcher drives three states:
| Detected running app | RIOJoy behavior |
|---|---|
| **Native game** (Firestorm, Red Planet) | **Release COM port, go fully dormant** — no serial, no HID, no overlay |
| **Supported non-native game** | Acquire port, load that game's profile, drive HID / keyboard / mouse / lamps / plasma / wallpaper |
| **Nothing / desktop** | Idle — port released or a configurable neutral default |
Config therefore holds: the per-game profile library, a **list of native games
to yield to**, and the executable→profile match rules. Manual override from the
tray menu is always available.
---
## Phases
### Phase 0 — Repo & scaffold ✅ (this commit)
- `git init`, remote `https://gitea.mysticmachines.com/VWE/riovjoy2.git`.
- Legacy C++ moved to `legacy/`; cockpit art to `docs/reference/`.
- Solution `RioJoy.sln` with `src/RioJoy.Core` (lib) + `src/RioJoy.Tray`
(tray app); `driver/` placeholder for the WDK project.
- This plan + [PROTOCOL.md](PROTOCOL.md).
### Phase 1 — Virtual HID driver (highest risk; do first)
- KMDF + VHF virtual HID gamepad: report descriptor = 6×16-bit axes, 1 hat,
96 buttons.
- Control device + custom IOCTL → `VhfReadReportSubmit`.
- Test harness (throwaway C#) wiggles axes/buttons; verify in `joy.cpl`.
- Test-signing setup for the cabinets.
### Phase 2 — Serial + RIO protocol core (`RioJoy.Core`)
- `SerialPort` wrapper; packet parser/builder (length table, 7-bit checksum,
ACK/NAK, framing resync).
- Analog poll timer + >5 s reset-recovery.
- **Clean COM-port acquire/release** (foundation for serial yield).
- Verify against hardware: version reply, check reply, analog stream.
### Phase 3 — Input mapping + output routing
- Port `iRIO` decode and routing precedence (keyboard/mouse/joy/hat/RIO-command).
- Keyboard/mouse via `SendInput` P/Invoke; lamp feedback over serial.
- HID-report feeder → the Phase 1 driver via `DeviceIoControl`.
### Phase 4 — Axis calibration + plasma display
- Port `UpdateJoystick/Throttle/Padal` math (deadzones, ratchet, rudder).
- Port the `CPlasma` ESC command set on the secondary COM port.
### Phase 5 — Tray app + profiles
- NotifyIcon + menu mirroring the legacy console menu (reset/recalibrate axes,
version/status, toggle raw-axis & poll-rate readouts, quit) + status/log window.
- **Profile library**, manual selection, and the **three-state auto-switch
watcher** (incl. native-game yield).
- Config persistence; auto-start.
### Phase 6 — Packaging / signing / deploy
- Driver install via `pnputil`; app installer; test-signing script.
- Cabinet deployment doc. (Production option: attestation signing.)
### Phase 7 — Profile/mapping editor + cockpit overlay generator
Replaces the legacy Google-Sheet → `.data` → GIMP → Script-Fu pipeline
(see [`docs/reference/customBackground/`](reference/customBackground/)).
- **Mapping editor:** per-button UI to set action + label + lamp, no hex
bit-twiddling; clone-from-existing profile.
- **Overlay generator:** render labels into named regions over a base cockpit
image using **SkiaSharp**, porting the auto-fit/justification logic from
`sg-goobie-MFD.scm`; export the per-profile wallpaper and re-apply via
`SystemParametersInfo`.
- **Region authoring:** extract the label rectangles from `riojoy.xcf` into a
`regions.json`; in-app box editor for future tweaks.
- The unified profile JSON supersedes both `RIO.ini` and the Google Sheet, with
importers for each.
- To confirm at Phase 7: target wallpaper resolution(s); static wallpaper vs.
live overlay (e.g. lit-button highlighting mirroring lamp state).
---
## Open items / risks
- **Driver signing** is the main friction point. Test-signing is fine for owned
cabinets; redistribution needs attestation signing (EV cert + Partner Center).
- **Input injection vs. session:** `SendInput` targets the interactive session —
fine for a tray app, which is why a Windows service was rejected.
- **Legacy quirks to decide on** (see PROTOCOL.md ⚠️ notes): disabled inbound
checksum verification; odd mouse-move deltas.