using System.Runtime.InteropServices; using RioJoy.Core.Calibration; using RioJoy.Core.Mapping; using RioJoy.Core.Output; // On-cabinet smoke test: drive the real HidFeederJoystickSink (which opens the // RioGamepad driver and submits reports via IOCTL_RIO_SUBMIT_REPORT) and read // the virtual gamepad back through winmm joyGetPosEx, confirming fed values // actually surface to the OS. Run after installing the driver: // dotnet run --project tools/RioJoySmokeTest -c Release // Exit code: 0 = all passed, 1 = a check failed, 2 = driver/joystick not found. const uint JOY_RETURNALL = 0x000000FF; int failures = 0; string lastName = ""; void Check(string what, bool ok) { Console.WriteLine($" [{(ok ? "PASS" : "FAIL")}] {what}"); if (!ok) failures++; } Console.WriteLine("== RioJoy feeder -> driver smoke test =="); if (!HidFeederJoystickSink.TryCreate(out var sink) || sink is null) { Console.WriteLine(" [FAIL] HidFeederJoystickSink.TryCreate returned false (driver not present?)."); return 2; } using (sink) { Console.WriteLine(" [PASS] Opened RioGamepad device interface."); int joyId = FindJoyId(); if (joyId < 0) { Console.WriteLine(" [FAIL] No winmm joystick found to read back from."); return 2; } Console.WriteLine($" Reading back via winmm joystick id {joyId} (\"{lastName}\")."); // 1. X axis: min / mid / max (RioHidReport logical max = 32767 -> joyGetPosEx ~0..65535) sink.SetAxis(JoyAxis.X, 0); var lo = Read(joyId); sink.SetAxis(JoyAxis.X, 16384); var mid = Read(joyId); sink.SetAxis(JoyAxis.X, 32767); var hi = Read(joyId); Console.WriteLine($" X(min)={lo.X} X(mid)={mid.X} X(max)={hi.X}"); Check("X axis tracks min 61000); // reset X to centre so it doesn't confuse later reads sink.SetAxis(JoyAxis.X, 16384); // 2. Z axis independent move sink.SetAxis(JoyAxis.Z, 32767); var z = Read(joyId); Console.WriteLine($" Z(max)={z.Z}"); Check("Z axis reaches max", z.Z > 61000); sink.SetAxis(JoyAxis.Z, 16384); // 3. Buttons: 1 and 5 sink.SetButton(1, true); var b1 = Read(joyId); Check("button 1 down -> bit0 set", (b1.Buttons & 0x1) != 0); sink.SetButton(5, true); var b5 = Read(joyId); Check("button 5 down -> bit4 set", (b5.Buttons & 0x10) != 0); sink.SetButton(1, false); sink.SetButton(5, false); var b0 = Read(joyId); Check("buttons released -> none set (low 8)", (b0.Buttons & 0xFF) == 0); // 4. Hat / POV sink.SetHat(RioHat.Up); var hUp = Read(joyId); Check("hat Up -> POV 0", hUp.Pov == 0); sink.SetHat(RioHat.Right); var hR = Read(joyId); Check("hat Right -> POV 9000", hR.Pov == 9000); sink.SetHat(RioHat.Centered); var hC = Read(joyId); Check("hat Centered -> POV centered (0xFFFF)", hC.Pov == 0xFFFF); } Console.WriteLine(failures == 0 ? "== ALL CHECKS PASSED ==" : $"== {failures} CHECK(S) FAILED =="); return failures == 0 ? 0 : 1; // ---- winmm read-back helpers ------------------------------------------------ static (uint X, uint Y, uint Z, uint Buttons, uint Pov) Read(int id) { Thread.Sleep(120); // let the report propagate through the HID stack var info = new JOYINFOEX { dwSize = (uint)Marshal.SizeOf(), dwFlags = JOY_RETURNALL }; uint rc = joyGetPosEx(id, ref info); if (rc != 0) throw new InvalidOperationException($"joyGetPosEx failed: {rc}"); return (info.dwXpos, info.dwYpos, info.dwZpos, info.dwButtons, info.dwPOV); } int FindJoyId() { int n = (int)joyGetNumDevs(); int firstPresent = -1; for (int id = 0; id < n; id++) { var caps = new JOYCAPS(); if (joyGetDevCapsW(id, ref caps, (uint)Marshal.SizeOf()) != 0) continue; var info = new JOYINFOEX { dwSize = (uint)Marshal.SizeOf(), dwFlags = JOY_RETURNALL }; if (joyGetPosEx(id, ref info) != 0) continue; // not present string name = caps.szPname ?? ""; 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.IndexOf("RIOJoy", StringComparison.OrdinalIgnoreCase) >= 0) { lastName = name; return id; } } return firstPresent; } [DllImport("winmm.dll")] static extern uint joyGetNumDevs(); [DllImport("winmm.dll")] static extern uint joyGetPosEx(int uJoyID, ref JOYINFOEX pji); [DllImport("winmm.dll", CharSet = CharSet.Unicode)] static extern uint joyGetDevCapsW(int uJoyID, ref JOYCAPS pjc, uint cbjc); [StructLayout(LayoutKind.Sequential)] struct JOYINFOEX { public uint dwSize, dwFlags, dwXpos, dwYpos, dwZpos, dwRpos, dwUpos, dwVpos, dwButtons, dwButtonNumber, dwPOV, dwReserved1, dwReserved2; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct JOYCAPS { public ushort wMid, wPid; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szPname; public uint wXmin, wXmax, wYmin, wYmax, wZmin, wZmax, wNumButtons, wPeriodMin, wPeriodMax, wRmin, wRmax, wUmin, wUmax, wVmin, wVmax, wCaps, wMaxAxes, wNumAxes, wMaxButtons; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string szRegKey; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public string szOEMVxD; }