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>
169 lines
8.6 KiB
Markdown
169 lines
8.6 KiB
Markdown
# 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.
|