# 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.