Rename the package payload folder to RIOJoy (was riojoy) so it extracts
to C:\games\RIOJoy, and add a pre-uninstall path the console can run
before deleting the folder.
- pre-uninstall.bat (inside RIOJoy\): self-elevating entry point that
runs uninstall-rio.ps1.
- uninstall-rio.ps1: stops the tray app, removes the HKLM Run entry,
uninstalls ViGEmBus (-KeepDriver to skip), and clears per-user config
for every profile (-KeepConfig to skip). Idempotent and non-fatal.
- build-package.ps1 bundles both; README documents the uninstall flow.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
deploy/ builds a single zip for TeslaConsole: the self-contained tray app and
the signed ViGEmBus installer under a riojoy\ folder, with postinstall.bat at the
root. postinstall elevates and runs riojoy\install-rio.ps1, which installs
ViGEmBus silently (if absent) and registers RIOJoy to start at logon — no cert
trust, test signing, Secure Boot change, or reboot.
Also guard against two instances fighting over the COM port (machine-wide +
per-user autostart both firing): a per-session named mutex makes a second launch
exit immediately. Built zips (dist/) and the bundled installer (deploy/vendor/)
are git-ignored.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the test-signed RioGamepad driver as the default joystick output with a
ViGEmBus virtual Xbox 360 controller (Nefarius.ViGEm.Client) — properly signed,
so it installs with no test-signing / Secure Boot change / reboot.
The coordinator now selects ViGEm first, then the RioGamepad HID feeder, then a
no-op. The XInput layout caps joystick buttons at 11, so the editor's joystick
picker lists the named Xbox buttons (A, B, X, Y, LB, RB, Back, Start, L3, R3,
Guide); the hat maps to the D-pad and the six axes to the sticks + triggers.
Bind anything beyond 11 to the keyboard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a "Triggers:" field under the profile name that edits the profile's
MatchExecutables (comma-separated, .exe optional). On save, the listed
foreground executables auto-activate the profile in Auto mode — no more
hand-editing config.json.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Profile name field in the editor (rename, with empty/duplicate checks);
"Edit profile" becomes a submenu listing each profile plus "New profile…".
- "Send button output to the PC" toggle: editor sessions route keyboard/mouse
and joystick through gates (GatedInputSink/GatedJoystickSink) that stay closed
until the user opts in, so editing never injects input by default.
- Save profile now confirms ("Saved ✓"/"Save failed") — the write was silent.
- A lit button renders dim at idle and bright only when physically pressed, so
the press stays visible (three states: off / dim-lit / bright-held).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SendInput declared its parameter as Span<INPUT>, which cannot be marshalled
(MarshalDirectiveException) — the first keyboard/mouse output threw on the
serial read thread and killed the link. Pass a blittable INPUT[] instead;
add a regression test.
Also: defend the runtime so a faulty output sink can never tear down the
serial link (output is best-effort), and dim lit lamps on the first reply
from the board rather than in Start() — lamp commands sent in the first ms
after the DTR reset (on port open) are dropped before the board has booted.
Verified end-to-end against the partial RIO on COM1 (keypad types to the PC,
joystick feeds, comms stay up; lit MFD buttons come up dim).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
While a profile is being edited, light every pressed button on the RIO
(RioRuntime.EchoAllLamps, enabled for editor sessions) regardless of mapping,
so a physical press always responds. On the panel, a held button now
brightens the cell in its own colour (red MFD / blue keypad / yellow board)
instead of a cyan outline.
Surface the RIO version/check replies (RioRuntime.VersionReceived/
CheckReceived) and show them grouped in a new "RIO reply" box below the
command buttons when "Request version & status" is clicked. Drop the two
diagnostic readout-toggle buttons (comms bring-up leftovers).
Verified against the partial RIO on COM1 (firmware 4.2; MFD + keypad light
and report; missing-board status shown).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A console diagnostic that opens the RIO COM port, runs the real serial link,
and logs every event (buttons, keypad, axis, version/check, control bytes,
framing). Flashes all lamps once to prove the PC->RIO path and echoes a lamp
on each button press. Verified end-to-end against a partial RIO on COM1
(firmware 4.2; MFD + keypad inputs and the throttle axis all decode).
dotnet run --project tools/RioSerialMonitor -- COM1 30
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Profile editor:
- Cockpit-style encoder gauges centered over the Upper Middle MFD: Z left,
X/Y right, and L/R/Rz arranged as a big "U".
- [JoyStick] section surfaced as a vertical "Axis" toggle column (6 inverts
+ ZR mix), written back to the profile's calibration.
- Tighten the panel layout (close gaps, trim empty border columns); colour the
Secondary/Screen columns yellow; rename the "Joystick / Hat" board to
"Joystick"; add an "Import .ini..." tray entry.
- "RIO commands (live)" button group (all RioCommandCodes) fired at the live RIO.
Serial-port handling:
- Start dormant; only take the COM port for a profiled game (auto-switch) or
while editing, so the native games can open the port without clashing.
- Editor sessions hold the port but route keyboard/mouse/joystick to no-op
sinks (NullInputSink), so button function can be checked without injecting
input; RioRuntime.ButtonActivity drives a live cyan press indicator and
lamp feedback still applies.
- AutoSwitchWatcher.Reset() re-syncs the watcher after an editor session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Strip a surrounding pair of double quotes from the [Plasma] Greeting on
import (the gsheet "INI_V2" output writes Greeting="..."), and add the
master button-layout spreadsheet that documents the RIO.ini format and the
16-bit map-word encoding. Test covers the real sheet output (modifier-stacked
words, keypad VK words, RIO command words) round-tripping verbatim.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reworked the profile/mapping editor to mirror the original unfinished Win32
RIO driver's control-panel design (docs/Win32RIO, by FASA / Michel Lowrance)
instead of the flat config-sheet grid. The wallpaper region positions are a
VGA chroma-split display artifact, not the cockpit's logical shape.
- RioJoy.Core.Editing.CockpitPanel: the functional layout — five MFD clusters,
four board columns (Throttle/Secondary/Screen/Joystick-Hat), an encoder-gauge
strip, and the two later-added 4x4 keypads (Internal/External). Places every
address 0x00-0x47 / 0x50-0x6F exactly once (unit-tested).
- Tray PanelView/PanelCanvas render the panel; ProfileEditorForm drives it.
Buttons show label + assigned function (ButtonBinding.Describe -> CTL-N, JOY1,
POV-U, Mse LB, RIO command names; unit-tested). Lamp shade is driven by the
IsLit flag (not by whether a function is assigned); keypads are neutral blue
with no Lit checkbox. Added an Unassign button next to Apply.
- Removed the superseded sheet-grid UI (SheetView/SheetCanvas); SheetLayout +
config-sheet.csv stay as reference data.
docs/Win32RIO: the original driver (tasgame.sys, a 32-bit kernel HID minidriver
that opened the serial port, set baud/8N1/DTR, and spoke the *identical* RIO
protocol -- AnalogRequest/Reply, Button Pressed/Released, Check/Lamp/Reset/
Version -- validating our protocol port), oemsetup.inf, RemoteDriver.doc, and the
extracted control-panel / game-controllers mockups.
~236 xUnit tests green. PLAN.md updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Overlay generator (pure, tested) — RioJoy.Core/Overlay:
- FontFitter ports the legacy calc-fontsize auto-fit search (validated against a
brute-force oracle); OverlayLayoutEngine ports create-data-layer's fit + h/v
justification and adds per-region 90 deg CCW rotation; OverlayTemplate/Region +
OverlayTemplateStore hold cell geometry/colour/rotation (regions.json).
- GoobieDataImporter parses the legacy .data label sheet into label rows.
- src/RioJoy.Overlay: SkiaSharp rasterizer (SkiaTextMeasurer shared with the engine
so measured layout == drawn output; SkiaOverlayRenderer -> PNG;
ProfileWallpaperGenerator). Verified end-to-end on the real cockpit art
(regions.json + riojoy.png + TEST.data) by OverlayRenderIntegrationTests.
- RioJoy.Tray/WallpaperApplier applies via SystemParametersInfo; RioCoordinator
generates+applies on profile activation (opt-in via AppConfig.OverlayTemplatePath).
Region geometry — tools/XcfRegionExtract parses riojoy.xcf (a GIMP-format binary
reader, no GIMP needed) into the 119-cell regions.json: per-layer offsets/size/
font/colour, with 90 deg CCW rotation on a-00 + b-10..b-1F. Base image riojoy.png.
NOTE: the wallpaper positions are a VGA chroma-split display artifact (6 displays),
not the cockpit's logical layout.
Mapping editor (first cut) — RioJoy.Tray/Editor/ProfileEditorForm renders the
config sheet's logical grid (SheetLayout parses the sheet CSV export). The iRIO
word is edited via ButtonBinding <-> RioMapEntry.Create (no hex bit-twiddling), with
a context-sensitive picker: keyboard keys by name (KeyCatalog VK names), joystick
Button N, hat direction, mouse/RIO-command enum names; modifiers only for keyboard.
Opened from the tray ("Edit profile..."). The sheet-grid layout is a starting point
that needs rework from a better-formatted source.
~220 xUnit tests green. Docs (PLAN.md, README) updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The RioGamepad driver now installs and enumerates, so the user-mode
feeder path is verifiable. Add tools/RioJoySmokeTest, a standalone
on-cabinet utility that drives the real HidFeederJoystickSink (open the
device, submit reports via IOCTL_RIO_SUBMIT_REPORT) and reads the gamepad
back through winmm joyGetPosEx, asserting axes (min/mid/max), buttons,
and the POV hat all surface to the OS. Verified: all checks pass against
the installed driver.
Update docs to match reality (PLAN.md predated the HidFeederJoystickSink
commit): Phase 1 is now test-signed/installed/verified with the VHF
LowerFilters requirement noted; the stale "NullJoystickSink placeholder"
remainders in Phases 3 and 5 are corrected to reflect the wired,
verified feeder. driver/README.md notes the end-to-end verification.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The driver installed but the device failed with Code 31
(CM_PROB_FAILED_ADD): VhfCreate returned STATUS_INVALID_DEVICE_REQUEST
(0xC0000010) because vhf.sys was never attached beneath the FDO. Add the
required LowerFilters=vhf AddReg to the INF so VHF loads as a lower
filter; the device now starts clean and enumerates in joy.cpl.
A VHF virtual HID device cannot supply a HID product string (VHF_CONFIG
has no string field and VHF owns IOCTL_HID_GET_STRING), so register the
DirectInput OEMName (VID_1209&PID_5249 -> RIOJoy Virtual Gamepad) in
install.ps1 -CreateDevice and remove it in uninstall.ps1, giving the
controller a proper name instead of the generic VHF default.
Also fix a non-ASCII em-dash in sign.ps1 that broke parsing under
Windows PowerShell 5.1 (UTF-8 without BOM), and document the Code 52 /
Code 31 / friendly-name troubleshooting in driver/README.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the virtual gamepad deployable on owned cabinets and wire the real
user-mode feeder:
- driver/sign.ps1 (non-admin): create a self-signed code-signing cert, build the
catalog with inf2cat, and SHA-256-sign RioGamepad.sys + .cat (embed-sign the sys
before cataloguing so the .cat matches). Exports RIOJoyTest.cer. Verified
end-to-end against the EWDK (signability + catalog clean; both files signed).
- driver/install.ps1 (admin, two-phase): trust the cert (LocalMachine Root +
TrustedPublisher), stage the package (pnputil /add-driver), enable test signing;
after reboot, -CreateDevice runs devgen to create root\RioGamepad so PnP installs
it. uninstall.ps1 reverses it.
- RioJoy.Core.Output.HidFeederJoystickSink: opens the driver by
GUID_DEVINTERFACE_RIOGAMEPAD (SetupAPI), maintains a RioHidReport, and submits it
via DeviceIoControl(IOCTL_RIO_SUBMIT_REPORT) on each axis/button/hat change.
RioCoordinator now uses it when the driver is present and falls back to the no-op
sink otherwise (status shows "[no joystick driver]").
- gitignore the signing outputs (driver/package/, *.cat); driver/README.md gets the
full build → sign → install → joy.cpl workflow.
Remaining = the actual elevated install + reboot + joy.cpl verification on the
cabinet (admin steps), and on-hardware confirmation of the feeder.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author the custom virtual HID gamepad that replaces vJoy, and pin its wire
format on both sides. Builds clean to RioGamepad.sys against the EWDK
(KMDF 1.15 + VHF, x64, warnings-as-errors).
driver/RioGamepad/:
- ReportDescriptor.h: 6x16-bit axes (X,Y,Z,Rx,Ry,Rz), one 4-direction hat with
null state, and 96 buttons — the legacy vJoy layout. 25-byte input report.
- Device.c/Driver.c: KMDF root-enumerated device that creates the VHF virtual HID
device (VhfCreate in DeviceAdd, VhfStart in D0Entry, VhfDelete on cleanup) and
exposes a device interface + IOCTL_RIO_SUBMIT_REPORT that forwards the caller's
report bytes to VhfReadReportSubmit. Thin relay: no report logic in the kernel.
- Public.h: device-interface GUID, IOCTL, and the report byte layout shared with
the C# client. RioGamepad.inf + build.cmd (EWDK build, catalog/sign disabled).
src/RioJoy.Core/Hid/RioHidReport.cs: packs AxisOutputs + hat + 96 buttons into
the exact 25-byte report (LE axes, hat nibble with 0x0F=centered, button bitmap).
13 new xUnit tests (136 total).
Remaining (deploy-side): test-sign + pnputil install + verify in joy.cpl, and
wire the real DeviceIoControl feeder sink (replacing NullJoystickSink). The
EWDK's in-build catalog task (DrvCat) can't load Microsoft.Kits.Logger on this
image, so the .cat is produced with inf2cat/signtool at install time instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the Core pieces into a runnable tray app with per-game profiles and the
three-state serial-yield auto-switch:
- Profiles/: RioProfile + AppConfig model; ConfigStore (System.Text.Json,
round-tripped); RioIniImporter ports the legacy RIO.ini (button table, invert
flags, plasma greeting); AutoSwitchResolver + AutoSwitchWatcher resolve the
foreground executable into Yield (native game) / Activate (profile) / Idle, with
native always winning and change-only notifications. IForegroundProcessProvider
abstracts the OS.
- RioRuntime assembles a profile's live pipeline: serial ButtonPressed/Released +
KeyPressed/Released → InputRouter (via RioAddress); AnalogReply → AxisCalibrator
→ the six joystick axes; RIO commands → calibration resets + version/check
requests + lamp re-init. SerialLampSink sends lamp feedback over the link;
NullJoystickSink is a placeholder until the Phase 1 HID feeder exists.
- RioJoy.Tray: NotifyIcon menu mirroring the legacy console menu (axis resets,
version/status, raw-axes & poll-rate toggles, quit) + profile selection
(auto vs. manual) + "start with Windows"; RioCoordinator owns the serial
acquire/release tied to the watcher (native-game COM-port yield). OS adapters:
ForegroundProcessProvider (Win32 foreground PID→exe) and AutoStartManager (HKCU
Run key).
- tests: 18 new xUnit tests (123 total) for config round-trip, ini import,
the three-state resolver + watcher, and RioRuntime end-to-end over the fake
transport (button→joystick, keypad-offset→keyboard, analog→six axes).
The joystick output stays a no-op until the Phase 1 driver; on-cabinet
verification of the acquire/release lifecycle remains.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the analog calibration math and the plasma/VFD command set:
- Calibration/: AxisCalibrator ports UpdateThrottle/UpdatePadal/UpdateJoystick
(riovjoy2.cpp#L1504+) — throttle deadzone + ratchet field, pedal deadzones,
joystick X/Y auto-ranging from observed min/max, rudder mixing (enableZR), and
the per-axis invert flags. Stateful (start positions, last outputs, observed
extremes) like the legacy globals, with the RIOcmd axis resets. Final outputs
clamp to the documented 0..32766 range (also guards a legacy compounding quirk).
AxisOutputs carries the six values; IJoystickSink gains SetAxis(JoyAxis,value)
so calibrated axes reach the HID feeder.
- Plasma/: PlasmaCommands builds the CPlasma ESC sequences (clear/cursor/font/
attr/box draw+fill/text) + GetFontSize + the PlasmaPosText auto-fit/centering;
PlasmaDisplay writes them over the secondary COM transport.
- tests: 21 new xUnit tests (105 total) for throttle proportional/saturation/
invert, pedal ZR mix vs. direct, joystick centering/direction/invert/reset, the
output clamp, and plasma byte sequences/font sizes/auto-fit positioning.
Hardware verification of axis feel + plasma output remains; the game-specific
PlasmaScoreDraw layout is deferred to profile content (Phase 5/7).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the iRIO decode and Press_V2/Release_V2 routing into testable C#:
- Mapping/: RioMapEntry decodes the 16-bit map word (lamp/mouse/hat/joy/extended/
alt/ctrl/shift flags + value) and resolves the routing Kind by the legacy
precedence (joy+hat+mouse => RIO command; none => keyboard; else joy>hat>mouse).
RioAddress translates button/keypad events to table addresses (+0x50/+0x60
keypad offsets); RioInputMap is the 112-entry per-profile table replacing the
hard-coded iRIO[].
- InputRouter ports Press_V2/Release_V2: modifier press/release ordering,
scancode keys, joystick buttons, POV hat, mouse move/click (deltas corrected
per PROTOCOL.md), RIO-command dispatch, and lamp feedback (bright on press, dim
on release; RIO commands carry none). InitializeLamps() dims all lamp entries.
- Output is split behind sink interfaces (IInputSink/IJoystickSink/ILampSink/
IRioCommandSink) so routing is pure + unit-tested; Output/SendInputSink is the
real SendInput keyboard/mouse adapter (scancode injection).
- tests: 30 new xUnit tests (84 total) for entry decode, address translation,
and router routing/precedence/lamp/modifier ordering via a recording sink.
The joystick sink's real adapter (HID feeder -> RioGamepad via DeviceIoControl)
is blocked on the Phase 1 driver; routing already targets IJoystickSink.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Port the RIO wire protocol from legacy/riovjoy2.cpp into testable C#:
- Protocol/: command + length table, 7-bit checksum, packet builder, and a
streaming receive-side framing state machine (PacketParser) that mirrors the
legacy ReadCommBlock framing/resync (high-bit-mid-packet abort). Typed RIO->PC
decodes: AnalogReport (14-bit sign-extend), VersionInfo, CheckStatus; lamp-state
composition.
- Serial/: RioSerialLink drives an async receive loop with ACK/NAK reply policy
(legacy force-accept vs. opt-in VerifyInboundChecksum), the analog poll timer,
and the >5s reset-recovery watchdog. IRioTransport abstracts the COM port; the
SerialPort-backed transport does 9600 8N1 + DTR reset pulse, and acquire/release
is just create/dispose (foundation for native-game serial yield).
- tests/RioJoy.Core.Tests: 54 xUnit tests covering checksum, framing/resync,
builder round-trips, analog sign-extension + sentinel rejection, lamp combos,
and the read loop driven against an in-memory fake transport.
Hardware verification (version/check/analog against a cabinet) remains; it can't
be done off-device.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>