Decompiled source of RVWheelSupport v0.2.2
shimloader/mod/RVWheelSupport/Companion/RVWheelSetup.exe
Decompiled a week ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Windows.Forms; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace RVWheel; internal static class SDL { public struct GuidBytes { public ulong A; public ulong B; } private const string Lib = "SDL2.dll"; [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SDL_Init(uint flags); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void SDL_Quit(); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SDL_SetHint(string name, string value); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr SDL_GetError(); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void SDL_PumpEvents(); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void SDL_JoystickUpdate(); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SDL_NumJoysticks(); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SDL_JoystickGetDeviceInstanceID(int index); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr SDL_JoystickOpen(int index); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void SDL_JoystickClose(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr SDL_JoystickName(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr SDL_JoystickPath(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr SDL_JoystickGetSerial(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern GuidBytes SDL_JoystickGetGUID(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void SDL_JoystickGetGUIDString(GuidBytes guid, StringBuilder output, int size); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SDL_JoystickNumAxes(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SDL_JoystickNumButtons(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int SDL_JoystickNumHats(IntPtr joystick); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern byte SDL_JoystickGetHat(IntPtr joystick, int hat); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern short SDL_JoystickGetAxis(IntPtr joystick, int axis); [DllImport("SDL2.dll", CallingConvention = CallingConvention.Cdecl)] public static extern byte SDL_JoystickGetButton(IntPtr joystick, int button); public static string Text(IntPtr p) { if (p == IntPtr.Zero) { return ""; } int i; for (i = 0; Marshal.ReadByte(p, i) != 0; i++) { } byte[] array = new byte[i]; Marshal.Copy(p, array, 0, i); return Encoding.UTF8.GetString(array); } } internal sealed class Device { public int Instance; public int Axes; public int Buttons; public int Hats; public IntPtr Handle; public string Key; public string Name; public override string ToString() { return Name + " (" + Axes + " axes, " + Buttons + " buttons, " + Hats + " POV)"; } } internal sealed class Devices : IDisposable { public List<Device> Items = new List<Device>(); public Devices() { SDL.SDL_SetHint("SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS", "1"); if (SDL.SDL_Init(16896u) != 0) { throw new Exception(SDL.Text(SDL.SDL_GetError())); } Refresh(); } public bool Refresh() { SDL.SDL_PumpEvents(); HashSet<int> ids = new HashSet<int>(); for (int i = 0; i < SDL.SDL_NumJoysticks(); i++) { ids.Add(SDL.SDL_JoystickGetDeviceInstanceID(i)); } bool result = Items.Any((Device x) => !ids.Contains(x.Instance)); foreach (Device item in Items.Where((Device x) => !ids.Contains(x.Instance)).ToList()) { SDL.SDL_JoystickClose(item.Handle); Items.Remove(item); } for (int num = 0; num < SDL.SDL_NumJoysticks(); num++) { int id = SDL.SDL_JoystickGetDeviceInstanceID(num); if (!Items.Any((Device x) => x.Instance == id)) { IntPtr intPtr = SDL.SDL_JoystickOpen(num); if (!(intPtr == IntPtr.Zero)) { StringBuilder stringBuilder = new StringBuilder(33); SDL.SDL_JoystickGetGUIDString(SDL.SDL_JoystickGetGUID(intPtr), stringBuilder, 33); string text = SDL.Text(SDL.SDL_JoystickGetSerial(intPtr)); string text2 = SDL.Text(SDL.SDL_JoystickPath(intPtr)); string key = string.Concat(stringBuilder, "|", (text.Length > 0) ? ("serial:" + text) : ("path:" + text2)); Items.Add(new Device { Instance = id, Handle = intPtr, Key = key, Name = SDL.Text(SDL.SDL_JoystickName(intPtr)), Axes = SDL.SDL_JoystickNumAxes(intPtr), Buttons = SDL.SDL_JoystickNumButtons(intPtr), Hats = Math.Max(0, SDL.SDL_JoystickNumHats(intPtr)) }); result = true; } } } return result; } public Device Resolve(string key) { List<Device> list = Items.Where((Device x) => x.Key == key).ToList(); if (list.Count != 1) { return null; } return list[0]; } public void Update() { SDL.SDL_PumpEvents(); SDL.SDL_JoystickUpdate(); } public void Dispose() { foreach (Device item in Items) { SDL.SDL_JoystickClose(item.Handle); } SDL.SDL_Quit(); } } [DataContract] internal sealed class Binding { [DataMember] public string DeviceKey = ""; [DataMember] public int Axis; [DataMember] public bool IsButton; [DataMember] public int Minimum = -32768; [DataMember] public int Center; [DataMember] public int Maximum = 32767; [DataMember] public double Deadzone = 0.02; [DataMember] public bool Invert; [DataMember] public bool FirstCaptured; [DataMember] public bool CenterCaptured; [DataMember] public bool LastCaptured; [DataMember] public bool DefaultSteeringRange = true; [DataMember] public bool PositionPattern; [DataMember] public int[] PatternAxes; [DataMember] public int[] PatternTolerance; [DataMember] public int[] PatternButtons; [DataMember] public int[] PatternHats; public bool CustomSteering { get { if (FirstCaptured && CenterCaptured && LastCaptured) { if (Minimum >= Center || Center >= Maximum) { if (Minimum > Center) { return Center > Maximum; } return false; } return true; } return false; } } [OnDeserializing] private void Defaults(StreamingContext context) { DefaultSteeringRange = true; } public bool Valid(bool steering) { if (IsButton) { if (!steering && !string.IsNullOrEmpty(DeviceKey) && Axis >= 0) { return Axis < 512; } return false; } if (steering && DefaultSteeringRange && !CustomSteering) { if (!string.IsNullOrEmpty(DeviceKey) && Axis >= 0 && Axis < 256 && Deadzone >= 0.0) { return Deadzone <= 0.4; } return false; } if (!string.IsNullOrEmpty(DeviceKey) && Axis >= 0 && Axis < 256 && FirstCaptured && LastCaptured && Minimum >= -32768 && Minimum <= 32767 && Maximum >= -32768 && Maximum <= 32767 && !double.IsNaN(Deadzone) && Deadzone >= 0.0 && Deadzone <= 0.4) { if (!steering) { return Math.Abs(Maximum - Minimum) > 100; } if (CenterCaptured) { if (Minimum >= Center || Center >= Maximum) { if (Minimum > Center) { return Center > Maximum; } return false; } return true; } return false; } return false; } public double Normalize(int raw, bool steering) { if (!Valid(steering)) { return 0.0; } if (IsButton) { return (raw != 0) ? 1 : 0; } double val; if (steering) { val = ((!CustomSteering) ? ((double)raw / (double)((raw >= 0) ? 32767 : 32768)) : (((long)(raw - Center) * (long)(Maximum - Center) >= 0) ? ((double)(raw - Center) / (double)(Maximum - Center)) : ((0.0 - (double)(raw - Center)) / (double)(Minimum - Center)))); val = Math.Max(-1.0, Math.Min(1.0, val)); if (Invert) { val = 0.0 - val; } if (!(Math.Abs(val) <= Deadzone)) { return (double)Math.Sign(val) * (Math.Abs(val) - Deadzone) / (1.0 - Deadzone); } return 0.0; } val = Math.Max(0.0, Math.Min(1.0, (double)(raw - Minimum) / (double)(Maximum - Minimum))); if (Invert) { val = 1.0 - val; } if (!(val <= Deadzone)) { return (val - Deadzone) / (1.0 - Deadzone); } return 0.0; } public double VisualPosition(int raw) { if (!Valid(steering: true)) { return 0.0; } double num = ((!CustomSteering) ? ((double)raw / (double)((raw >= 0) ? 32767 : 32768)) : (((long)(raw - Center) * (long)(Maximum - Center) >= 0) ? ((double)(raw - Center) / (double)(Maximum - Center)) : ((0.0 - (double)(raw - Center)) / (double)(Minimum - Center)))); return Math.Max(-1.0, Math.Min(1.0, Invert ? (0.0 - num) : num)); } } internal static class ActionCatalog { public static readonly string[] Names = new string[5] { "Headlights / light cycle", "Horn (hold)", "Park toggle", "Gear up", "Gear down" }; public static Binding[] NewBindings() { return Names.Select((string n) => new Binding { IsButton = true }).ToArray(); } } [DataContract] internal sealed class Profile { [DataMember] public int Version = 4; [DataMember] public Binding Steering = new Binding(); [DataMember] public Binding Throttle = new Binding(); [DataMember] public Binding Brake = new Binding(); [DataMember] public Binding Handbrake = new Binding(); [DataMember] public Binding Clutch = new Binding(); [DataMember] public Binding Engine = new Binding { IsButton = true }; [DataMember] public bool EnableInput = true; [DataMember] public Binding[] Gears = NewGears(); [DataMember] public Binding[] Actions = ActionCatalog.NewBindings(); [DataMember] public int DrivingFov; [DataMember] public int VisualWheelDegrees = 900; [DataMember] public bool RealisticShifting = true; [DataMember] public bool ForceFeedbackEnabled; [DataMember] public int ForceFeedbackStrength = 30; [DataMember] public bool ForceFeedbackOutputEnabled; [DataMember] public string ForceFeedbackDeviceInstance = ""; public Binding[] All => new Binding[5] { Steering, Throttle, Brake, Handbrake, Clutch }; private static Binding[] NewGears() { return (from i in Enumerable.Range(0, 8) select new Binding { IsButton = true }).ToArray(); } [OnDeserializing] private void Defaults(StreamingContext context) { Version = 0; EnableInput = true; VisualWheelDegrees = 900; RealisticShifting = true; ForceFeedbackEnabled = false; ForceFeedbackStrength = 30; ForceFeedbackOutputEnabled = false; ForceFeedbackDeviceInstance = ""; } [OnDeserialized] private void Upgrade(StreamingContext context) { if (Version < 2) { ForceFeedbackEnabled = false; ForceFeedbackStrength = 30; } if (Version < 3) { ForceFeedbackOutputEnabled = false; } if (Version < 4) { ForceFeedbackDeviceInstance = ""; } Version = 4; if (Clutch == null) { Clutch = new Binding(); } if (Engine == null) { Engine = new Binding { IsButton = true }; } if (Gears == null || Gears.Length != 8 || Gears.Any((Binding b) => b == null)) { Gears = NewGears(); } Binding[] array = ActionCatalog.NewBindings(); if (Actions != null) { for (int num = 0; num < Math.Min(Actions.Length, array.Length); num++) { if (Actions[num] != null) { array[num] = Actions[num]; } } } Actions = array; if (DrivingFov != 0) { DrivingFov = Math.Max(60, Math.Min(150, DrivingFov)); } ForceFeedbackStrength = Math.Max(0, Math.Min(100, ForceFeedbackStrength)); } } internal sealed class AxisRow { public Binding Binding; public bool Steering; public string Name; public ComboBox DeviceBox; public ComboBox AxisBox; public Label Live; public ProgressBar Gauge; public int? Raw; public double Value; } internal static class GearPattern { public static bool Matches(Binding binding, int[] axes, int[] down, int[] relevant, int[] hats = null) { if (!binding.PositionPattern || binding.PatternAxes == null || binding.PatternTolerance == null || binding.PatternButtons == null || binding.PatternAxes.Length != axes.Length || binding.PatternTolerance.Length != axes.Length) { return false; } for (int i = 0; i < axes.Length; i++) { if (binding.PatternTolerance[i] < 0 || binding.PatternTolerance[i] > 1024 || Math.Abs((long)axes[i] - (long)binding.PatternAxes[i]) > binding.PatternTolerance[i]) { return false; } } foreach (int value in relevant) { if (binding.PatternButtons.Contains(value) != down.Contains(value)) { return false; } } if (hats != null && hats.Length > 0 && (binding.PatternHats == null || !binding.PatternHats.SequenceEqual(hats))) { return false; } return true; } } internal static class Calibration { public static int ChooseAxis(int[] low, int[] high, int[] center, bool steering) { int num = -1; for (int i = 0; i < low.Length; i++) { if (high[i] - low[i] >= 64 && (!steering || (center[i] - low[i] >= 8 && high[i] - center[i] >= 8)) && (num < 0 || high[i] - low[i] > high[num] - low[num])) { num = i; } } return num; } } internal static class Bridge { public static string Line(string session, long sequence, bool active, double[] values, long enginePresses, int gear, int mask, bool clutchRequired = false, int fov = 0, int heldActions = 0, long[] actionCounts = null, int visualDegrees = 900, double visualAngle = 0.0, bool ffbEnabled = false, int ffbStrength = 30) { return string.Format(CultureInfo.InvariantCulture, "RVW6 {0} {1} {2} {3:F6} {4:F6} {5:F6} {6:F6} {7:F6} {8} {9} {10} {11} {12} {13} {14} {15} {16:F6} {17} {18}\n", session, sequence, active ? 1 : 0, values[0], values[1], values[2], values[3], values[4], enginePresses, gear, mask, clutchRequired ? 1 : 0, fov, heldActions, string.Join(",", actionCounts ?? new long[ActionCatalog.Names.Length]), visualDegrees, visualAngle, ffbEnabled ? 1 : 0, Math.Max(0, Math.Min(100, ffbStrength))); } public static void Write(string path, string contents) { string text = path + ".tmp"; File.WriteAllText(text, contents, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } } internal sealed class SetupForm : Form { private sealed class CaptureState { public AxisRow Row; public string DeviceKey; public int[] Start; public int[] Low; public int[] High; public Stopwatch Clock = Stopwatch.StartNew(); } private sealed class PositionCapture { public AxisRow Row; public string Key; public DateTime Start; public DateTime Stable; public int[] Low; public int[] High; public int[] Buttons; public int[] Hats; } private CaptureState capture; private PositionCapture positionCapture; private AxisRow buttonCapture; private DateTime buttonDeadline; private readonly HashSet<string> buttonBaseline; private readonly Devices devices; private Profile profile; private readonly List<AxisRow> rows; private readonly List<AxisRow> gearRows; private readonly List<AxisRow> actionRows; private readonly long[] actionCounts; private readonly bool[] actionWasDown; private readonly CheckBox enabled; private readonly CheckBox realisticShifting; private readonly CheckBox forceFeedbackEnabled; private readonly CheckBox forceFeedbackOutputEnabled; private readonly ComboBox forceFeedbackDevice; private readonly Button refreshForceFeedbackDevices; private readonly NumericUpDown forceFeedbackStrength; private readonly Label forceFeedbackStatus; private readonly Label status; private readonly Label buttons; private readonly Label gameStatus; private readonly Label gearSignals; private readonly Label gearCaptureStatus; private readonly Label gearGameStatus; private readonly ProgressBar gearCaptureProgress; private readonly Timer timer; private readonly string root; private readonly string session; private readonly DirectInputForceFeedback forceOutput; private readonly Stopwatch refresh; private long sequence; private long enginePresses; private bool loading; private bool loadingForceFeedbackDevices; private bool engineHeld; private List<ForceFeedbackDevice> forceFeedbackDevices; private readonly Stopwatch forceStatusRefresh; private string lastForceStatus; private string notice; private string gameText; private DateTime noticeUntil; private int writeFailures; [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out uint processId); private static bool GameFocused() { try { GetWindowThreadProcessId(GetForegroundWindow(), out var processId); return Process.GetProcessById((int)processId).ProcessName.Equals("Ride-Win64-Shipping", StringComparison.OrdinalIgnoreCase); } catch { return false; } } public SetupForm(Devices source) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Expected O, but got Unknown //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Expected O, but got Unknown //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Expected O, but got Unknown //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Expected O, but got Unknown //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Expected O, but got Unknown //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Expected O, but got Unknown //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Expected O, but got Unknown //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Expected O, but got Unknown //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown //IL_0276: Unknown result type (might be due to invalid IL or missing references) //IL_027d: Expected O, but got Unknown //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Expected O, but got Unknown //IL_03a3: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Expected O, but got Unknown //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03bb: Expected O, but got Unknown //IL_03cb: Unknown result type (might be due to invalid IL or missing references) //IL_03d1: Expected O, but got Unknown //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Expected O, but got Unknown //IL_0405: Unknown result type (might be due to invalid IL or missing references) //IL_040b: Expected O, but got Unknown //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_041e: Expected O, but got Unknown //IL_0442: Unknown result type (might be due to invalid IL or missing references) //IL_0464: Unknown result type (might be due to invalid IL or missing references) //IL_046b: Expected O, but got Unknown //IL_0498: Unknown result type (might be due to invalid IL or missing references) //IL_049f: Expected O, but got Unknown //IL_04ba: Unknown result type (might be due to invalid IL or missing references) //IL_04c1: Expected O, but got Unknown //IL_04f3: Unknown result type (might be due to invalid IL or missing references) //IL_04fa: Expected O, but got Unknown //IL_05c5: Unknown result type (might be due to invalid IL or missing references) //IL_05cc: Expected O, but got Unknown //IL_064f: Unknown result type (might be due to invalid IL or missing references) //IL_0659: Expected O, but got Unknown //IL_066a: Unknown result type (might be due to invalid IL or missing references) //IL_0671: Expected O, but got Unknown //IL_068e: Unknown result type (might be due to invalid IL or missing references) //IL_06b8: Unknown result type (might be due to invalid IL or missing references) //IL_06bf: Expected O, but got Unknown //IL_0701: Unknown result type (might be due to invalid IL or missing references) //IL_0708: Expected O, but got Unknown //IL_0721: Unknown result type (might be due to invalid IL or missing references) //IL_0728: Expected O, but got Unknown //IL_073f: Unknown result type (might be due to invalid IL or missing references) //IL_0767: Unknown result type (might be due to invalid IL or missing references) //IL_076e: Expected O, but got Unknown //IL_07d1: Unknown result type (might be due to invalid IL or missing references) //IL_07d8: Expected O, but got Unknown //IL_082e: Unknown result type (might be due to invalid IL or missing references) //IL_0835: Expected O, but got Unknown //IL_0843: Unknown result type (might be due to invalid IL or missing references) //IL_09d7: Unknown result type (might be due to invalid IL or missing references) //IL_09e1: Expected O, but got Unknown //IL_0a38: Unknown result type (might be due to invalid IL or missing references) //IL_0a42: Expected O, but got Unknown //IL_0a42: Unknown result type (might be due to invalid IL or missing references) //IL_0a49: Expected O, but got Unknown //IL_0a73: Unknown result type (might be due to invalid IL or missing references) //IL_0ac8: Unknown result type (might be due to invalid IL or missing references) //IL_0acf: Expected O, but got Unknown //IL_0bfc: Unknown result type (might be due to invalid IL or missing references) //IL_0c03: Expected O, but got Unknown //IL_0c1d: Unknown result type (might be due to invalid IL or missing references) //IL_0c59: Unknown result type (might be due to invalid IL or missing references) //IL_0c60: Expected O, but got Unknown //IL_0c7a: Unknown result type (might be due to invalid IL or missing references) //IL_0ca4: Unknown result type (might be due to invalid IL or missing references) //IL_0cab: Expected O, but got Unknown //IL_0cc5: Unknown result type (might be due to invalid IL or missing references) //IL_0ceb: Unknown result type (might be due to invalid IL or missing references) //IL_0cf2: Expected O, but got Unknown //IL_0d3f: Unknown result type (might be due to invalid IL or missing references) //IL_0d46: Expected O, but got Unknown //IL_0d6a: Unknown result type (might be due to invalid IL or missing references) //IL_0d8c: Unknown result type (might be due to invalid IL or missing references) //IL_0d93: Expected O, but got Unknown //IL_0dcd: Unknown result type (might be due to invalid IL or missing references) //IL_0dd7: Expected O, but got Unknown //IL_0dde: Unknown result type (might be due to invalid IL or missing references) //IL_0ed9: Unknown result type (might be due to invalid IL or missing references) //IL_0ee3: Expected O, but got Unknown //IL_0eee: Unknown result type (might be due to invalid IL or missing references) //IL_0ef5: Expected O, but got Unknown //IL_0f09: Unknown result type (might be due to invalid IL or missing references) //IL_0f10: Expected O, but got Unknown //IL_0f6a: Unknown result type (might be due to invalid IL or missing references) //IL_0f71: Expected O, but got Unknown //IL_0f8b: Unknown result type (might be due to invalid IL or missing references) //IL_1058: Unknown result type (might be due to invalid IL or missing references) //IL_105f: Expected O, but got Unknown FormClosingEventHandler val = null; buttonBaseline = new HashSet<string>(); rows = new List<AxisRow>(); gearRows = new List<AxisRow>(); actionRows = new List<AxisRow>(); actionCounts = new long[ActionCatalog.Names.Length]; actionWasDown = new bool[ActionCatalog.Names.Length]; CheckBox val2 = new CheckBox(); ((Control)val2).Text = "Enable driving input"; ((Control)val2).AutoSize = true; enabled = val2; CheckBox val3 = new CheckBox(); ((Control)val3).Text = "Require the clutch to engage gears (realistic shifting)"; ((Control)val3).AutoSize = true; realisticShifting = val3; CheckBox val4 = new CheckBox(); ((Control)val4).Text = "Enable force feedback"; ((Control)val4).AutoSize = true; forceFeedbackEnabled = val4; CheckBox val5 = new CheckBox(); ((Control)val5).Text = "Send force to selected device"; ((Control)val5).AutoSize = true; forceFeedbackOutputEnabled = val5; ComboBox val6 = new ComboBox(); ((Control)val6).Width = 320; val6.DropDownStyle = (ComboBoxStyle)2; forceFeedbackDevice = val6; Button val7 = new Button(); ((Control)val7).Text = "Refresh devices"; ((Control)val7).AutoSize = true; refreshForceFeedbackDevices = val7; NumericUpDown val8 = new NumericUpDown(); val8.Minimum = 0m; val8.Maximum = 100m; val8.Increment = 1m; ((Control)val8).Width = 55; forceFeedbackStrength = val8; Label val9 = new Label(); ((Control)val9).AutoSize = true; ((Control)val9).MaximumSize = new Size(1000, 0); forceFeedbackStatus = val9; Label val10 = new Label(); ((Control)val10).AutoSize = true; status = val10; Label val11 = new Label(); ((Control)val11).AutoSize = true; buttons = val11; Label val12 = new Label(); ((Control)val12).AutoSize = true; gameStatus = val12; Label val13 = new Label(); ((Control)val13).AutoSize = true; ((Control)val13).MaximumSize = new Size(1030, 0); gearSignals = val13; Label val14 = new Label(); ((Control)val14).AutoSize = true; ((Control)val14).MaximumSize = new Size(1030, 0); ((Control)val14).Text = "Capture result: none yet"; gearCaptureStatus = val14; Label val15 = new Label(); ((Control)val15).AutoSize = true; ((Control)val15).MaximumSize = new Size(1030, 0); gearGameStatus = val15; ProgressBar val16 = new ProgressBar(); val16.Minimum = 0; val16.Maximum = 1000; ((Control)val16).Width = 980; ((Control)val16).Height = 20; gearCaptureProgress = val16; Timer val17 = new Timer(); val17.Interval = 16; timer = val17; root = AppDomain.CurrentDomain.BaseDirectory; session = Guid.NewGuid().ToString("N"); forceOutput = new DirectInputForceFeedback(); refresh = Stopwatch.StartNew(); forceFeedbackDevices = new List<ForceFeedbackDevice>(); forceStatusRefresh = Stopwatch.StartNew(); lastForceStatus = ""; notice = ""; gameText = "Waiting for game status"; ((Form)this)..ctor(); devices = source; profile = LoadProfile(); ((Control)this).Text = "RV Wheel Support — setup alpha"; ((Form)this).ClientSize = new Size(1120, 800); ((Control)this).MinimumSize = new Size(1100, 680); ((Control)this).Font = new Font("Segoe UI", 10f); ((ContainerControl)this).AutoScaleMode = (AutoScaleMode)2; TabControl val18 = new TabControl(); ((Control)val18).Dock = (DockStyle)5; TabControl val19 = val18; TabPage val20 = new TabPage("Driving controls"); TabPage val21 = new TabPage("Gear bindings"); val19.TabPages.Add(val20); val19.TabPages.Add(val21); ((Control)this).Controls.Add((Control)(object)val19); TabPage val22 = new TabPage("Driving actions"); val19.TabPages.Add(val22); FlowLayoutPanel val23 = new FlowLayoutPanel(); ((Control)val23).Dock = (DockStyle)5; ((ScrollableControl)val23).AutoScroll = true; val23.FlowDirection = (FlowDirection)1; val23.WrapContents = false; ((Control)val23).Padding = new Padding(12); FlowLayoutPanel val24 = val23; ((Control)val22).Controls.Add((Control)(object)val24); ControlCollection controls = ((Control)val24).Controls; Label val25 = new Label(); ((Control)val25).AutoSize = true; ((Control)val25).MaximumSize = new Size(1000, 0); ((Control)val25).Text = "Optional driving buttons: cycle headlights, hold horn, toggle parking and shift up/down. Enable realistic shifting to require the clutch for gear changes."; controls.Add((Control)(object)val25); FlowLayoutPanel val26 = new FlowLayoutPanel(); ((Control)val26).Width = 1020; ((Control)val26).Height = 45; FlowLayoutPanel val27 = val26; CheckBox val28 = new CheckBox(); ((Control)val28).Text = "Override driving FOV"; val28.Checked = profile.DrivingFov > 0; ((Control)val28).AutoSize = true; CheckBox fovEnabled = val28; NumericUpDown val29 = new NumericUpDown(); val29.Minimum = 60m; val29.Maximum = 150m; val29.Value = ((profile.DrivingFov > 0) ? profile.DrivingFov : 110); ((Control)val29).Width = 65; NumericUpDown fovValue = val29; Action saveFov = delegate { profile.DrivingFov = (fovEnabled.Checked ? ((int)fovValue.Value) : 0); SaveProfile(); }; fovEnabled.CheckedChanged += delegate { saveFov(); }; fovValue.ValueChanged += delegate { saveFov(); }; ((Control)val27).Controls.Add((Control)(object)fovEnabled); ((Control)val27).Controls.Add((Control)(object)fovValue); ControlCollection controls2 = ((Control)val27).Controls; Label val30 = new Label(); ((Control)val30).Text = "60–150 degrees; applies in the driver seat only"; ((Control)val30).AutoSize = true; controls2.Add((Control)(object)val30); ((Control)val24).Controls.Add((Control)(object)val27); for (int num = 0; num < ActionCatalog.Names.Length; num++) { profile.Actions[num].IsButton = true; AxisRow axisRow = CreateRow(ActionCatalog.Names[num], profile.Actions[num], steering: false, buttonOnly: false, actionOnly: true); actionRows.Add(axisRow); ((Control)val24).Controls.Add((Control)((Control)axisRow.Live).Tag); } TableLayoutPanel val31 = new TableLayoutPanel(); ((Control)val31).Dock = (DockStyle)5; val31.ColumnCount = 1; val31.RowCount = 13; ((Control)val31).Padding = new Padding(16); ((ScrollableControl)val31).AutoScroll = true; TableLayoutPanel val32 = val31; ((Control)val20).Controls.Add((Control)(object)val32); TableLayoutControlCollection controls3 = val32.Controls; Label val33 = new Label(); ((Control)val33).AutoSize = true; ((Control)val33).Text = "Auto calibration: start with wheel CENTERED or pedal RELEASED, click Auto calibrate, then sweep that control through its full range."; ((ControlCollection)controls3).Add((Control)(object)val33); profile.VisualWheelDegrees = Math.Max(90, Math.Min(2520, profile.VisualWheelDegrees)); FlowLayoutPanel val34 = new FlowLayoutPanel(); ((Control)val34).Width = 1030; ((Control)val34).Height = 95; FlowLayoutPanel val35 = val34; Label val36 = new Label(); ((Control)val36).AutoSize = true; ((Control)val36).Text = "Cockpit wheel animation: enter your wheel's total rotation limit (visual only)"; ((Control)val36).Margin = new Padding(4); Label val37 = val36; ((Control)val35).Controls.Add((Control)(object)val37); val35.SetFlowBreak((Control)(object)val37, true); TrackBar val38 = new TrackBar(); val38.Minimum = 90; val38.Maximum = 2520; val38.Value = profile.VisualWheelDegrees; val38.TickFrequency = 180; val38.SmallChange = 10; val38.LargeChange = 90; ((Control)val38).Width = 630; ((Control)val38).Height = 42; TrackBar spanSlider = val38; NumericUpDown val39 = new NumericUpDown(); val39.Minimum = 90m; val39.Maximum = 2520m; val39.Value = profile.VisualWheelDegrees; val39.Increment = 10m; ((Control)val39).Width = 85; NumericUpDown spanNumber = val39; Label val40 = new Label(); ((Control)val40).AutoSize = true; ((Control)val40).Padding = new Padding(0, 5, 0, 0); Label spanSummary = val40; bool spanUpdating = false; Action<int> saveSpan = delegate(int value) { if (!spanUpdating) { spanUpdating = true; profile.VisualWheelDegrees = value; spanSlider.Value = value; spanNumber.Value = value; ((Control)spanSummary).Text = value + "° total / " + ((double)value / 2.0).ToString("0.#", CultureInfo.InvariantCulture) + "° each way"; spanUpdating = false; SaveProfile(); } }; spanSlider.ValueChanged += delegate { saveSpan(spanSlider.Value); }; spanNumber.ValueChanged += delegate { saveSpan((int)spanNumber.Value); }; ((Control)val35).Controls.Add((Control)(object)spanSlider); ((Control)val35).Controls.Add((Control)(object)spanNumber); ((Control)val35).Controls.Add((Control)(object)spanSummary); ((ControlCollection)val32.Controls).Add((Control)(object)val35); ((Control)spanSummary).Text = profile.VisualWheelDegrees + "° total / " + ((double)profile.VisualWheelDegrees / 2.0).ToString("0.#", CultureInfo.InvariantCulture) + "° each way"; string[] array = new string[5] { "Steering", "Throttle", "Brake", "Handbrake (optional)", "Clutch (optional)" }; for (int num2 = 0; num2 < 5; num2++) { AxisRow axisRow2 = CreateRow(array[num2], profile.All[num2], num2 == 0); rows.Add(axisRow2); ((ControlCollection)val32.Controls).Add((Control)((Control)axisRow2.Live).Tag); } profile.Engine.IsButton = true; AxisRow axisRow3 = CreateRow("Engine on / off (optional button)", profile.Engine, steering: false); rows.Add(axisRow3); ((ControlCollection)val32.Controls).Add((Control)((Control)axisRow3.Live).Tag); FlowLayoutPanel val41 = new FlowLayoutPanel(); ((Control)val41).AutoSize = true; ((Control)val41).Width = 1030; val41.FlowDirection = (FlowDirection)1; val41.WrapContents = false; ((Control)val41).Margin = new Padding(0, 4, 0, 8); FlowLayoutPanel val42 = val41; realisticShifting.Checked = profile.RealisticShifting; realisticShifting.CheckedChanged += delegate { profile.RealisticShifting = realisticShifting.Checked; SaveProfile(); }; ((Control)val42).Controls.Add((Control)(object)realisticShifting); FlowLayoutPanel val43 = new FlowLayoutPanel(); ((Control)val43).AutoSize = true; ((Control)val43).Width = 1020; val43.WrapContents = true; FlowLayoutPanel val44 = val43; forceFeedbackEnabled.Checked = profile.ForceFeedbackEnabled; forceFeedbackOutputEnabled.Checked = profile.ForceFeedbackOutputEnabled; forceFeedbackStrength.Value = profile.ForceFeedbackStrength; forceFeedbackEnabled.CheckedChanged += delegate { profile.ForceFeedbackEnabled = forceFeedbackEnabled.Checked; SaveProfile(); }; forceFeedbackOutputEnabled.CheckedChanged += delegate { profile.ForceFeedbackOutputEnabled = forceFeedbackOutputEnabled.Checked; SaveProfile(); }; forceFeedbackDevice.SelectedIndexChanged += delegate { if (!loadingForceFeedbackDevices) { ForceFeedbackDevice forceFeedbackDevice = this.forceFeedbackDevice.SelectedItem as ForceFeedbackDevice; profile.ForceFeedbackDeviceInstance = ((forceFeedbackDevice == null) ? "" : forceFeedbackDevice.InstanceGuid.ToString("D")); SaveProfile(); } }; ((Control)refreshForceFeedbackDevices).Click += delegate { RefreshForceFeedbackChoices(autoSelectSingle: false); }; forceFeedbackStrength.ValueChanged += delegate { profile.ForceFeedbackStrength = (int)forceFeedbackStrength.Value; SaveProfile(); }; ((Control)val44).Controls.Add((Control)(object)forceFeedbackEnabled); ((Control)val44).Controls.Add((Control)(object)forceFeedbackOutputEnabled); ControlCollection controls4 = ((Control)val44).Controls; Label val45 = new Label(); ((Control)val45).Text = "Output device"; ((Control)val45).AutoSize = true; ((Control)val45).Padding = new Padding(4, 4, 0, 0); controls4.Add((Control)(object)val45); ((Control)val44).Controls.Add((Control)(object)forceFeedbackDevice); ((Control)val44).Controls.Add((Control)(object)refreshForceFeedbackDevices); ControlCollection controls5 = ((Control)val44).Controls; Label val46 = new Label(); ((Control)val46).Text = "Strength"; ((Control)val46).AutoSize = true; ((Control)val46).Padding = new Padding(4, 4, 0, 0); controls5.Add((Control)(object)val46); ((Control)val44).Controls.Add((Control)(object)forceFeedbackStrength); ControlCollection controls6 = ((Control)val44).Controls; Label val47 = new Label(); ((Control)val47).Text = "0–100% (hardware output capped at 30% for first tests)"; ((Control)val47).AutoSize = true; ((Control)val47).Padding = new Padding(0, 4, 0, 0); controls6.Add((Control)(object)val47); ((Control)val42).Controls.Add((Control)(object)val44); ControlCollection controls7 = ((Control)val42).Controls; Label val48 = new Label(); ((Control)val48).AutoSize = true; ((Control)val48).MaximumSize = new Size(1000, 0); ((Control)val48).Text = "Strength scales the game's wheel-force signal. Select the DirectInput force-feedback device that should receive output. Initial hardware output is capped at 30%; use the wheel driver's app for centering and end stops."; controls7.Add((Control)(object)val48); ((Control)val42).Controls.Add((Control)(object)forceFeedbackStatus); ((ControlCollection)val32.Controls).Add((Control)(object)val42); FlowLayoutPanel val49 = new FlowLayoutPanel(); ((Control)val49).Dock = (DockStyle)5; ((ScrollableControl)val49).AutoScroll = true; val49.FlowDirection = (FlowDirection)1; val49.WrapContents = false; ((Control)val49).Padding = new Padding(12); FlowLayoutPanel val50 = val49; ((Control)val21).Controls.Add((Control)(object)val50); ControlCollection controls8 = ((Control)val50).Controls; Label val51 = new Label(); ((Control)val51).AutoSize = true; ((Control)val51).Text = "Select the shifter, put it in a gear, then Capture position and hold it still. Captures axes + buttons, including shared top/bottom signals. Unmatched positions select Neutral."; controls8.Add((Control)(object)val51); ((Control)val50).Controls.Add((Control)(object)gearSignals); ((Control)gearSignals).Font = new Font(((Control)this).Font, (FontStyle)1); ((Control)gearSignals).Padding = new Padding(6); ((Control)gearSignals).BackColor = Color.AliceBlue; ((Control)val50).Controls.Add((Control)(object)gearGameStatus); ((Control)val50).Controls.Add((Control)(object)gearCaptureStatus); ((Control)val50).Controls.Add((Control)(object)gearCaptureProgress); string[] array2 = new string[8] { "Park", "Reverse", "Neutral", "Gear 1", "Gear 2", "Gear 3", "Gear 4", "Gear 5" }; for (int num3 = 0; num3 < 8; num3++) { profile.Gears[num3].IsButton = true; AxisRow axisRow4 = CreateRow(array2[num3], profile.Gears[num3], steering: false, buttonOnly: true); gearRows.Add(axisRow4); ((Control)val50).Controls.Add((Control)((Control)axisRow4.Live).Tag); } FlowLayoutPanel val52 = new FlowLayoutPanel(); ((Control)val52).AutoSize = true; ((Control)val52).Dock = (DockStyle)1; FlowLayoutPanel val53 = val52; Button val54 = new Button(); ((Control)val54).Text = "Save profile"; ((Control)val54).AutoSize = true; Button val55 = val54; ((Control)val55).Click += delegate { SaveProfile(); }; ((Control)val53).Controls.Add((Control)(object)enabled); ((Control)val53).Controls.Add((Control)(object)val55); ControlCollection controls9 = ((Control)val53).Controls; Label val56 = new Label(); ((Control)val56).Text = "Input releases when the game loses focus."; ((Control)val56).AutoSize = true; ((Control)val56).Padding = new Padding(0, 5, 0, 0); controls9.Add((Control)(object)val56); ((ControlCollection)val32.Controls).Add((Control)(object)val53); ((ControlCollection)val32.Controls).Add((Control)(object)status); ((ControlCollection)val32.Controls).Add((Control)(object)gameStatus); ((ControlCollection)val32.Controls).Add((Control)(object)buttons); enabled.Checked = profile.EnableInput; enabled.CheckedChanged += delegate { profile.EnableInput = enabled.Checked; SaveProfile(); }; RefreshChoices(); RefreshForceFeedbackChoices(autoSelectSingle: true); timer.Tick += delegate { TickInput(); }; timer.Start(); if (val == null) { val = (FormClosingEventHandler)delegate { timer.Stop(); forceOutput.Dispose(); try { Bridge.Write(Path.Combine(root, "ffb-device-status.txt"), DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture) + " output stopped: setup closed\n"); } catch { } WriteNeutral(); SaveProfile(); }; } ((Form)this).FormClosing += val; } private Profile LoadProfile() { //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) string text = Path.Combine(root, "profile.json"); if (!File.Exists(text)) { return new Profile(); } try { using FileStream fileStream = File.OpenRead(text); Profile profile = (Profile)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(Profile))).ReadObject((Stream)fileStream); if (profile.Version < 2 || profile.Version > 4 || profile.All.Any((Binding x) => x == null)) { throw new Exception("Unsupported profile"); } return profile; } catch (Exception ex) { MessageBox.Show("Profile could not be loaded: " + ex.Message + "\nIt will be preserved; new settings start unbound."); File.Copy(text, text + ".invalid-" + DateTime.Now.ToString("yyyyMMddHHmmss"), overwrite: true); return new Profile(); } } private void SaveProfile() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) try { using MemoryStream memoryStream = new MemoryStream(); ((XmlObjectSerializer)new DataContractJsonSerializer(typeof(Profile))).WriteObject((Stream)memoryStream, (object)profile); Bridge.Write(Path.Combine(root, "profile.json"), Encoding.UTF8.GetString(memoryStream.ToArray())); } catch (Exception ex) { ((Control)status).Text = "Save failed: " + ex.Message; } } private AxisRow CreateRow(string name, Binding binding, bool steering, bool buttonOnly = false, bool actionOnly = false) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Expected O, but got Unknown //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Expected O, but got Unknown //IL_02e6: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Expected O, but got Unknown //IL_0363: Unknown result type (might be due to invalid IL or missing references) //IL_036a: Expected O, but got Unknown //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Expected O, but got Unknown //IL_04a8: Unknown result type (might be due to invalid IL or missing references) //IL_04af: Expected O, but got Unknown //IL_04f6: Unknown result type (might be due to invalid IL or missing references) //IL_04fd: Expected O, but got Unknown //IL_0538: Unknown result type (might be due to invalid IL or missing references) //IL_053f: Expected O, but got Unknown //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_05b3: Expected O, but got Unknown //IL_05d0: Unknown result type (might be due to invalid IL or missing references) //IL_05d7: Expected O, but got Unknown //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_06bb: Unknown result type (might be due to invalid IL or missing references) //IL_06c2: Expected O, but got Unknown //IL_06f1: Unknown result type (might be due to invalid IL or missing references) AxisRow axisRow = new AxisRow { Name = name, Binding = binding, Steering = steering }; ComboBox val = new ComboBox(); ((Control)val).Width = 300; val.DropDownStyle = (ComboBoxStyle)2; axisRow.DeviceBox = val; ComboBox val2 = new ComboBox(); ((Control)val2).Width = 60; val2.DropDownStyle = (ComboBoxStyle)2; axisRow.AxisBox = val2; Label val3 = new Label(); ((Control)val3).AutoSize = true; axisRow.Live = val3; AxisRow row = axisRow; GroupBox val4 = new GroupBox(); ((Control)val4).Text = name; ((Control)val4).Width = 1030; ((Control)val4).Height = 125; ((Control)val4).Margin = new Padding(0, 4, 0, 8); GroupBox val5 = val4; FlowLayoutPanel val6 = new FlowLayoutPanel(); ((Control)val6).Dock = (DockStyle)5; ((Control)val6).Padding = new Padding(8, 4, 0, 0); FlowLayoutPanel val7 = val6; ((Control)val5).Controls.Add((Control)(object)val7); ((Control)row.Live).MaximumSize = new Size(990, 0); ((Control)row.Live).Margin = new Padding(4, 8, 4, 4); ((Control)val7).Controls.Add((Control)(object)row.DeviceBox); if (!steering) { ComboBox val8 = new ComboBox(); ((Control)val8).Width = 80; val8.DropDownStyle = (ComboBoxStyle)2; ComboBox kind = val8; kind.Items.AddRange(new object[2] { "Axis", buttonOnly ? "Position" : "Button" }); ((ListControl)kind).SelectedIndex = (binding.IsButton ? 1 : 0); if (binding == profile.Engine || buttonOnly || actionOnly) { ((Control)kind).Enabled = false; } kind.SelectedIndexChanged += delegate { binding.IsButton = ((ListControl)kind).SelectedIndex == 1; binding.Axis = 0; ClearCalibration(binding); PopulateAxes(row, devices.Resolve(binding.DeviceKey)); SaveProfile(); }; ((Control)val7).Controls.Add((Control)(object)kind); } ((Control)val7).Controls.Add((Control)(object)row.AxisBox); row.DeviceBox.SelectedIndexChanged += delegate { if (!loading) { Device device = row.DeviceBox.SelectedItem as Device; string text = ((device == null) ? "" : device.Key); if (binding.DeviceKey != text) { ClearCalibration(binding); binding.PositionPattern = false; } binding.DeviceKey = text; PopulateAxes(row, device); SaveProfile(); } }; row.AxisBox.SelectedIndexChanged += delegate { if (!loading) { if (binding.Axis != ((ListControl)row.AxisBox).SelectedIndex) { ClearCalibration(binding); binding.PositionPattern = false; } binding.Axis = ((ListControl)row.AxisBox).SelectedIndex; SaveProfile(); } }; if (binding == profile.Engine || buttonOnly || actionOnly) { ((Control)val5).Height = (buttonOnly ? 105 : 100); ((Control)val5).Margin = new Padding(0, 3, 0, 4); ((Control)row.AxisBox).Visible = !buttonOnly; Button val9 = new Button(); ((Control)val9).Text = (buttonOnly ? "Capture position (3s)" : "Bind (10s)"); ((Control)val9).AutoSize = true; Button val10 = val9; ((Control)val10).Click += delegate { if (buttonOnly) { StartPositionCapture(row); } else { StartButtonCapture(row); } }; ((Control)val7).Controls.Add((Control)(object)val10); Button val11 = new Button(); ((Control)val11).Text = "Clear"; ((Control)val11).AutoSize = true; Button val12 = val11; ((Control)val12).Click += delegate { binding.DeviceKey = ""; RefreshChoices(); SaveProfile(); }; ((Control)val7).Controls.Add((Control)(object)val12); Label val13 = new Label(); ((Control)val13).Text = (buttonOnly ? "Hold gear still during capture." : (actionOnly ? "Bind a button for this driving action." : "One press toggles the RV engine.")); ((Control)val13).AutoSize = true; ((Control)val13).Padding = new Padding(0, 5, 0, 0); Label val14 = val13; ((Control)val7).Controls.Add((Control)(object)val14); val7.SetFlowBreak((Control)(object)val14, true); ((Control)val7).Controls.Add((Control)(object)row.Live); ((Control)row.Live).Tag = val5; return row; } string[] array = (steering ? new string[3] { "Read left", "Read center", "Read right" } : new string[2] { "Read released", "Read pressed" }); for (int num = 0; num < array.Length; num++) { int capture = num; Button val15 = new Button(); ((Control)val15).Text = array[num]; ((Control)val15).AutoSize = true; Button val16 = val15; ((Control)val16).Click += delegate { if (!row.Raw.HasValue) { ((Control)status).Text = "Select a connected device and axis first."; } else { if (capture == 0) { binding.Minimum = row.Raw.Value; binding.FirstCaptured = true; } else if (steering && capture == 1) { binding.Center = row.Raw.Value; binding.CenterCaptured = true; } else { binding.Maximum = row.Raw.Value; binding.LastCaptured = true; } SaveProfile(); } }; ((Control)val7).Controls.Add((Control)(object)val16); } Button val17 = new Button(); ((Control)val17).Text = "Auto calibrate (8s)"; ((Control)val17).AutoSize = true; Button val18 = val17; ((Control)val18).Click += delegate { StartCapture(row); }; ((Control)val7).Controls.Add((Control)(object)val18); CheckBox val19 = new CheckBox(); ((Control)val19).Text = "Invert"; val19.Checked = binding.Invert; ((Control)val19).AutoSize = true; CheckBox invert = val19; invert.CheckedChanged += delegate { binding.Invert = invert.Checked; SaveProfile(); }; ((Control)val7).Controls.Add((Control)(object)invert); val7.SetFlowBreak((Control)(object)invert, true); ControlCollection controls = ((Control)val7).Controls; Label val20 = new Label(); ((Control)val20).Text = "Deadzone %"; ((Control)val20).AutoSize = true; controls.Add((Control)(object)val20); NumericUpDown val21 = new NumericUpDown(); val21.Minimum = 0m; val21.Maximum = 40m; ((Control)val21).Width = 55; val21.Value = (decimal)(double.IsNaN(binding.Deadzone) ? 2.0 : Math.Max(0.0, Math.Min(40.0, binding.Deadzone * 100.0))); NumericUpDown deadzone = val21; deadzone.ValueChanged += delegate { binding.Deadzone = (double)deadzone.Value / 100.0; SaveProfile(); }; ((Control)val7).Controls.Add((Control)(object)deadzone); ((Control)val7).Controls.Add((Control)(object)row.Live); val7.SetFlowBreak((Control)(object)row.Live, true); AxisRow axisRow2 = row; ProgressBar val22 = new ProgressBar(); val22.Minimum = 0; val22.Maximum = 1000; ((Control)val22).Width = 960; ((Control)val22).Height = 14; ((Control)val22).Margin = new Padding(4, 2, 4, 0); axisRow2.Gauge = val22; ((Control)val7).Controls.Add((Control)(object)row.Gauge); ((Control)row.Live).Tag = val5; return row; } private static void ClearCalibration(Binding b) { b.FirstCaptured = (b.CenterCaptured = (b.LastCaptured = false)); } private void StartCapture(AxisRow row) { Device d = devices.Resolve(row.Binding.DeviceKey); if (d == null || d.Axes == 0 || row.Binding.IsButton) { ((Control)status).Text = "Auto calibration is for axes. Button mappings need no calibration."; return; } devices.Update(); int[] array = Enumerable.Range(0, d.Axes).Select((Func<int, int>)((int i) => SDL.SDL_JoystickGetAxis(d.Handle, i))).ToArray(); capture = new CaptureState { Row = row, DeviceKey = d.Key, Start = array, Low = (int[])array.Clone(), High = (int[])array.Clone() }; } private void UpdateCapture() { if (capture == null) { return; } CaptureState c = capture; Device device = devices.Resolve(c.DeviceKey); if (device == null || c.Row.Binding.DeviceKey != c.DeviceKey) { capture = null; ((Control)status).Text = "Calibration cancelled: device changed."; return; } for (int i = 0; i < device.Axes; i++) { int val = SDL.SDL_JoystickGetAxis(device.Handle, i); c.Low[i] = Math.Min(c.Low[i], val); c.High[i] = Math.Max(c.High[i], val); } if (c.Clock.ElapsedMilliseconds < 8000) { return; } capture = null; int num = Calibration.ChooseAxis(c.Low, c.High, c.Start, c.Row.Steering); string text = string.Join("\n", from num2 in Enumerable.Range(0, c.Low.Length) select "Axis " + num2 + ": start=" + c.Start[num2] + " min=" + c.Low[num2] + " max=" + c.High[num2]); File.WriteAllText(Path.Combine(root, "last-calibration.txt"), c.Row.Name + "\n" + text + "\nSelected=" + num); Binding binding = c.Row.Binding; if (num < 0) { Notify(c.Row.Steering ? "Steering calibration: start CENTERED, then sweep BOTH directions. No suitable axis moved on both sides of center." : "No clear movement detected. Fully press and release only this pedal."); return; } binding.Axis = num; binding.Center = c.Start[num]; if (c.Row.Steering) { binding.Minimum = c.Low[num]; binding.Maximum = c.High[num]; binding.CenterCaptured = true; } else { binding.Minimum = c.Start[num]; binding.Maximum = ((Math.Abs(c.High[num] - binding.Minimum) > Math.Abs(c.Low[num] - binding.Minimum)) ? c.High[num] : c.Low[num]); } binding.FirstCaptured = (binding.LastCaptured = true); PopulateAxes(c.Row, device); SaveProfile(); Notify(c.Row.Name + " calibrated on axis " + num + ". Enable driving input when ready."); } private void Notify(string text) { notice = text; noticeUntil = DateTime.UtcNow.AddSeconds(8.0); } private void StartButtonCapture(AxisRow row) { buttonCapture = row; buttonDeadline = DateTime.UtcNow.AddSeconds(10.0); buttonBaseline.Clear(); foreach (Device item in devices.Items) { for (int i = 0; i < item.Buttons; i++) { if (SDL.SDL_JoystickGetButton(item.Handle, i) != 0) { buttonBaseline.Add(item.Key + "#" + i); } } } ((Control)row.Live).Text = "LISTENING: release the current position, then engage " + row.Name; } private int[] DownButtons(Device d) { return (from i in Enumerable.Range(0, d.Buttons) where SDL.SDL_JoystickGetButton(d.Handle, i) != 0 select i).ToArray(); } private int[] Axes(Device d) { return Enumerable.Range(0, d.Axes).Select((Func<int, int>)((int i) => SDL.SDL_JoystickGetAxis(d.Handle, i))).ToArray(); } private int[] Hats(Device d) { return Enumerable.Range(0, d.Hats).Select((Func<int, int>)((int i) => SDL.SDL_JoystickGetHat(d.Handle, i))).ToArray(); } private void StartPositionCapture(AxisRow row) { Device device = devices.Resolve(row.Binding.DeviceKey); if (device == null) { ((Control)row.Live).Text = "Select the shifter device first."; ((Control)gearCaptureStatus).Text = row.Name + ": select a shifter device first."; return; } positionCapture = new PositionCapture { Row = row, Key = device.Key, Start = DateTime.UtcNow, Stable = DateTime.UtcNow }; gearCaptureProgress.Value = 0; ((Control)gearCaptureStatus).Text = "CAPTURING " + row.Name + " — hold that position. Reading axes, POV hats and buttons."; } private void UpdatePositionCapture() { if (positionCapture == null) { return; } PositionCapture c = positionCapture; Device device = devices.Resolve(c.Key); if (device == null || c.Row.Binding.DeviceKey != c.Key) { ((Control)gearCaptureStatus).Text = "CANCELLED: device changed. Old pattern kept."; positionCapture = null; return; } if ((DateTime.UtcNow - c.Start).TotalSeconds > 10.0) { ((Control)gearCaptureStatus).Text = "TIMED OUT: signals did not settle. Old pattern kept."; positionCapture = null; return; } gearCaptureProgress.Value = Math.Min(950, (int)((DateTime.UtcNow - c.Start).TotalMilliseconds / 3.0)); if ((DateTime.UtcNow - c.Start).TotalSeconds < 2.0) { ((Control)c.Row.Live).Text = "HOLD " + c.Row.Name + " — capturing full position in a moment..."; return; } int[] array = Axes(device); int[] array2 = DownButtons(device); int[] array3 = Hats(device); if (c.Low == null || !c.Buttons.SequenceEqual(array2) || !c.Hats.SequenceEqual(array3)) { c.Low = (int[])array.Clone(); c.High = (int[])array.Clone(); c.Buttons = array2; c.Hats = array3; c.Stable = DateTime.UtcNow; } for (int i = 0; i < array.Length; i++) { c.Low[i] = Math.Min(c.Low[i], array[i]); c.High[i] = Math.Max(c.High[i], array[i]); } if (Enumerable.Range(0, array.Length).Any((int num) => c.High[num] - c.Low[num] > 64)) { c.Low = null; return; } ((Control)c.Row.Live).Text = "HOLD " + c.Row.Name + " | axes=" + string.Join(",", array) + " buttons=" + string.Join(",", array2); if (!((DateTime.UtcNow - c.Stable).TotalMilliseconds < 600.0)) { Binding binding = c.Row.Binding; binding.PatternAxes = (from num in Enumerable.Range(0, array.Length) select (c.Low[num] + c.High[num]) / 2).ToArray(); binding.PatternTolerance = (from num in Enumerable.Range(0, array.Length) select Math.Max(4, (c.High[num] - c.Low[num]) * 3)).ToArray(); binding.PatternButtons = array2; binding.PatternHats = array3; binding.PositionPattern = true; positionCapture = null; gearCaptureProgress.Value = 1000; ((Control)gearCaptureStatus).Text = "SAVED " + c.Row.Name + " | axes=[" + string.Join(",", binding.PatternAxes) + "] POV=[" + string.Join(",", array3) + "] buttons=[" + string.Join(",", array2) + "]"; SaveProfile(); Notify(((Control)gearCaptureStatus).Text); } } private void UpdateButtonCapture() { if (buttonCapture == null) { return; } if (DateTime.UtcNow >= buttonDeadline) { ((Control)buttonCapture.Live).Text = "Binding timed out; old mapping kept."; buttonCapture = null; return; } foreach (Device item2 in devices.Items) { for (int i = 0; i < item2.Buttons; i++) { string item = item2.Key + "#" + i; if (SDL.SDL_JoystickGetButton(item2.Handle, i) == 0) { buttonBaseline.Remove(item); } else if (!buttonBaseline.Contains(item)) { buttonCapture.Binding.DeviceKey = item2.Key; buttonCapture.Binding.Axis = i; buttonCapture.Binding.IsButton = true; ((Control)buttonCapture.Live).Text = "Bound to " + item2.Name + ", button " + i; buttonCapture = null; RefreshChoices(); SaveProfile(); return; } } } } private int ReadGear(out string warning) { warning = ""; List<AxisRow> list = gearRows.Where((AxisRow r) => !string.IsNullOrEmpty(r.Binding.DeviceKey)).ToList(); if (list.Count == 0) { return -99; } List<int> list2 = new List<int>(); for (int num = 0; num < gearRows.Count; num++) { AxisRow row = gearRows[num]; Binding b = row.Binding; if (string.IsNullOrEmpty(b.DeviceKey)) { ((Control)row.Live).Text = "Unassigned"; continue; } Device device = devices.Resolve(b.DeviceKey); if (device == null) { warning = "Gear device missing"; ((Control)row.Live).Text = warning; return -99; } bool flag; if (b.PositionPattern) { int[] relevant = (from r in gearRows select r.Binding into x where x.DeviceKey == b.DeviceKey && x.PositionPattern && x.PatternButtons != null select x).SelectMany((Binding x) => x.PatternButtons).Distinct().ToArray(); flag = GearPattern.Matches(b, Axes(device), DownButtons(device), relevant, Hats(device)); ((Control)row.Live).Text = (flag ? "ENGAGED" : "Saved position") + " | axes=[" + string.Join(",", b.PatternAxes ?? new int[0]) + "] POV=[" + string.Join(",", b.PatternHats ?? new int[0]) + "] buttons=[" + string.Join(",", b.PatternButtons ?? new int[0]) + "]"; if (device.Hats > 0 && b.PatternHats == null) { ((Control)row.Live).Text = "Position predates POV support — missing POV data"; } } else { if (b.Axis < 0 || b.Axis >= device.Buttons) { ((Control)row.Live).Text = "Capture a position"; continue; } bool flag2 = gearRows.Any((AxisRow r) => r != row && !r.Binding.PositionPattern && r.Binding.DeviceKey == b.DeviceKey && r.Binding.Axis == b.Axis); flag = !flag2 && SDL.SDL_JoystickGetButton(device.Handle, b.Axis) != 0; ((Control)row.Live).Text = (flag2 ? "Shared button-only mapping: capture full position" : ("Legacy button " + b.Axis + (flag ? " ENGAGED" : ""))); if (flag2 && SDL.SDL_JoystickGetButton(device.Handle, b.Axis) != 0) { warning = "Shared shifter buttons need full position patterns"; } } if (flag) { list2.Add(num); } ((Control)row.Live).ForeColor = (flag ? Color.DarkGreen : (b.PositionPattern ? Color.DarkSlateGray : Color.DarkOrange)); } if (list2.Count > 1) { warning = "Multiple gear buttons active — gear command withheld"; return -99; } if (list2.Count != 1) { if (warning.Length <= 0) { return 2; } return -99; } return list2[0]; } private void ReadGameStatus() { try { using FileStream stream = new FileStream(Path.Combine(root, "game-status.txt"), FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using StreamReader streamReader = new StreamReader(stream); gameText = streamReader.ReadToEnd(); } catch (IOException) { } ((Control)gameStatus).Text = "Game: " + gameText; try { using FileStream stream2 = new FileStream(Path.Combine(root, "action-status.txt"), FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); using StreamReader streamReader2 = new StreamReader(stream2); Label obj = gameStatus; ((Control)obj).Text = ((Control)obj).Text + " | Actions: " + streamReader2.ReadToEnd(); } catch (IOException) { } ((Control)gearGameStatus).Text = ((Control)gameStatus).Text; } private void PopulateAxes(AxisRow row, Device d) { bool flag = loading; loading = true; row.AxisBox.Items.Clear(); int num = ((d != null) ? (row.Binding.IsButton ? d.Buttons : d.Axes) : 0); for (int i = 0; i < num; i++) { row.AxisBox.Items.Add((object)i); } ((ListControl)row.AxisBox).SelectedIndex = ((row.Binding.Axis >= 0 && row.Binding.Axis < num) ? row.Binding.Axis : (-1)); loading = flag; } private void RefreshChoices() { loading = true; foreach (AxisRow item in rows.Concat(gearRows).Concat(actionRows)) { item.DeviceBox.Items.Clear(); item.DeviceBox.Items.Add((object)"Unassigned"); foreach (Device item2 in devices.Items) { item.DeviceBox.Items.Add((object)item2); } Device device = devices.Resolve(item.Binding.DeviceKey); item.DeviceBox.SelectedItem = ((device == null) ? ((object)"Unassigned") : ((object)device)); PopulateAxes(item, device); } loading = false; } private void RefreshForceFeedbackChoices(bool autoSelectSingle) { string saved = profile.ForceFeedbackDeviceInstance ?? ""; try { forceFeedbackDevices = DirectInputProbe.EnumerateForceFeedbackDevices(); } catch (Exception ex) { ((Control)forceFeedbackStatus).Text = "DirectInput device scan failed: " + ex.Message; return; } loadingForceFeedbackDevices = true; this.forceFeedbackDevice.Items.Clear(); foreach (ForceFeedbackDevice forceFeedbackDevice2 in forceFeedbackDevices) { this.forceFeedbackDevice.Items.Add((object)forceFeedbackDevice2); } ForceFeedbackDevice forceFeedbackDevice = forceFeedbackDevices.FirstOrDefault((ForceFeedbackDevice d) => d.InstanceGuid.ToString("D").Equals(saved, StringComparison.OrdinalIgnoreCase)); if (forceFeedbackDevice != null) { this.forceFeedbackDevice.SelectedItem = forceFeedbackDevice; } else if (string.IsNullOrEmpty(saved) && autoSelectSingle && forceFeedbackDevices.Count == 1) { forceFeedbackDevice = forceFeedbackDevices[0]; this.forceFeedbackDevice.SelectedItem = forceFeedbackDevice; } else { ((ListControl)this.forceFeedbackDevice).SelectedIndex = -1; } loadingForceFeedbackDevices = false; if (forceFeedbackDevice != null && !forceFeedbackDevice.InstanceGuid.ToString("D").Equals(saved, StringComparison.OrdinalIgnoreCase)) { profile.ForceFeedbackDeviceInstance = forceFeedbackDevice.InstanceGuid.ToString("D"); SaveProfile(); } if (forceFeedbackDevices.Count == 0) { ((Control)forceFeedbackStatus).Text = "No DirectInput constant-force devices found. Connect the wheel and refresh."; } else if (forceFeedbackDevice == null) { ((Control)forceFeedbackStatus).Text = "Select the wheel base that should receive force."; } } private void TickInput() { try { if (refresh.ElapsedMilliseconds >= 1000) { if (devices.Refresh()) { enabled.Checked = false; RefreshChoices(); } ReadGameStatus(); ((Control)gearSignals).Text = string.Join(" | ", from d in (from r in gearRows select devices.Resolve(r.Binding.DeviceKey) into d where d != null select d).Distinct() select d.Name + "\nLIVE axes=[" + string.Join(",", Axes(d)) + "] POV=[" + string.Join(",", Hats(d)) + "] buttons=[" + string.Join(",", DownButtons(d)) + "]"); try { Bridge.Write(Path.Combine(root, "shifter-signals.txt"), ((Control)gearSignals).Text); } catch (IOException) { } refresh.Restart(); } devices.Update(); int num = 0; bool flag = false; double[] array = new double[5]; List<string> list = new List<string>(); bool flag2 = false; UpdateCapture(); UpdateButtonCapture(); UpdatePositionCapture(); for (int num2 = 0; num2 < rows.Count; num2++) { AxisRow axisRow = rows[num2]; Binding binding = axisRow.Binding; Device device = devices.Resolve(binding.DeviceKey); axisRow.Raw = null; if (device != null && binding.Axis >= 0 && binding.Axis < (binding.IsButton ? device.Buttons : device.Axes)) { axisRow.Raw = (binding.IsButton ? SDL.SDL_JoystickGetButton(device.Handle, binding.Axis) : SDL.SDL_JoystickGetAxis(device.Handle, binding.Axis)); } bool flag3 = axisRow.Raw.HasValue && binding.Valid(axisRow.Steering); if ((num2 < 3 || !string.IsNullOrEmpty(binding.DeviceKey)) && !flag3) { list.Add(axisRow.Name + ": unavailable"); } if (num2 < 3 && flag3) { num |= 1 << num2; } if (num2 == 5 && flag3) { flag = true; } axisRow.Value = (flag3 ? binding.Normalize(axisRow.Raw.Value, axisRow.Steering) : 0.0); if (axisRow.Gauge != null) { axisRow.Gauge.Value = Math.Max(0, Math.Min(1000, (int)(1000.0 * (axisRow.Steering ? ((axisRow.Value + 1.0) / 2.0) : axisRow.Value)))); } if (num2 < 5) { array[num2] = axisRow.Value; } else { flag2 = flag3 && axisRow.Value > 0.5; } ((Control)axisRow.Live).Text = string.Format(CultureInfo.InvariantCulture, "Raw {0} | Output {1:F3} | {2} [{3}, {4}, {5}]", axisRow.Raw.HasValue ? axisRow.Raw.Value.ToString() : "—", axisRow.Value, flag3 ? ((axisRow.Steering && !binding.CustomSteering) ? "Default SDL range" : (binding.IsButton ? "Button mapped" : "Calibrated")) : "Unavailable", binding.Minimum, binding.Center, binding.Maximum); } if (!rows[3].Raw.HasValue || !profile.Handbrake.Valid(steering: false)) { array[3] = -1.0; } if (!rows[4].Raw.HasValue || !profile.Clutch.Valid(steering: false)) { array[4] = -1.0; } string warning; int num3 = ReadGear(out warning); if (warning.Length > 0) { list.Add(warning); } int num4 = 0; for (int num5 = 0; num5 < actionRows.Count; num5++) { AxisRow axisRow2 = actionRows[num5]; Binding binding2 = axisRow2.Binding; Device device2 = devices.Resolve(binding2.DeviceKey); bool flag4 = device2 != null && binding2.Axis >= 0 && binding2.Axis < device2.Buttons; bool flag5 = flag4 && SDL.SDL_JoystickGetButton(device2.Handle, binding2.Axis) != 0; if (flag4) { flag = true; } if (flag5) { num4 |= 1 << num5; } axisRow2.Raw = (flag4 ? new int?(flag5 ? 1 : 0) : ((int?)null)); ((Control)axisRow2.Live).Text = (flag4 ? (flag5 ? "PRESSED" : ("Ready — button " + binding2.Axis)) : (string.IsNullOrEmpty(binding2.DeviceKey) ? "Unassigned" : "Device or button unavailable")); ((Control)axisRow2.Live).ForeColor = (flag5 ? Color.DarkGreen : Color.DarkSlateGray); } bool flag6 = num != 0 || flag || num3 != -99; bool flag7 = enabled.Checked && flag6 && capture == null && buttonCapture == null && positionCapture == null && GameFocused(); double num6 = 0.0; bool fresh = false; try { num6 = ForceFeedbackOutput.ReadFreshTorque(Path.Combine(root, "ffb-output.txt"), out fresh); } catch { } string text = forceOutput.Update(((Control)this).Handle, profile.ForceFeedbackDeviceInstance, profile.ForceFeedbackOutputEnabled && profile.ForceFeedbackEnabled && profile.ForceFeedbackStrength > 0 && flag7 && fresh, num6); ((Control)forceFeedbackStatus).Text = ((!profile.ForceFeedbackEnabled) ? "Force feedback model off" : ((!profile.ForceFeedbackOutputEnabled) ? "Model ready; physical output off" : (string.IsNullOrEmpty(profile.ForceFeedbackDeviceInstance) ? "Choose a DirectInput force feedback device" : text))); if (((Control)forceFeedbackStatus).Text != lastForceStatus || forceStatusRefresh.ElapsedMilliseconds >= 250) { string contents = string.Format(CultureInfo.InvariantCulture, "{0} output={1} model={2} strength={3} focused={4} inputActive={5} sampleFresh={6} torque={7:F6} device={8} status={9}\n", DateTime.UtcNow.ToString("O", CultureInfo.InvariantCulture), profile.ForceFeedbackOutputEnabled, profile.ForceFeedbackEnabled, profile.ForceFeedbackStrength, GameFocused(), flag7, fresh, num6, profile.ForceFeedbackDeviceInstance, ((Control)forceFeedbackStatus).Text); try { Bridge.Write(Path.Combine(root, "ffb-device-status.txt"), contents); } catch (IOException) { } lastForceStatus = ((Control)forceFeedbackStatus).Text; forceStatusRefresh.Restart(); } if (flag7 && flag2 && !engineHeld) { enginePresses++; } engineHeld = flag2; for (int num7 = 0; num7 < actionRows.Count; num7++) { bool flag8 = (num4 & (1 << num7)) != 0; if (flag7 && flag8 && !actionWasDown[num7]) { actionCounts[num7]++; } actionWasDown[num7] = flag8; } if (!flag7) { Array.Clear(array, 0, array.Length); } double visualAngle = (rows[0].Raw.HasValue ? (profile.Steering.VisualPosition(rows[0].Raw.Value) * (double)profile.VisualWheelDegrees / 2.0) : 0.0); try { Bridge.Write(Path.Combine(root, "input.txt"), Bridge.Line(session, ++sequence, flag7, array, enginePresses, num3, num, profile.RealisticShifting && !string.IsNullOrEmpty(profile.Clutch.DeviceKey), profile.DrivingFov, flag7 ? num4 : 0, actionCounts, profile.VisualWheelDegrees, visualAngle, profile.ForceFeedbackEnabled, profile.ForceFeedbackStrength)); writeFailures = 0; } catch (IOException) { writeFailures++; } ((Control)status).Text = ((!flag6) ? ("No usable controls — " + string.Join("; ", list)) : (flag7 ? ("Driving input ACTIVE | gear=" + num3) : (enabled.Checked ? "Armed — switch to RV There Yet to drive." : "Ready — check Enable driving input."))); if (flag6 && list.Count > 0) { Label obj2 = status; ((Control)obj2).Text = ((Control)obj2).Text + " | " + string.Join("; ", list); } if (capture != null) { ((Control)status).Text = "CALIBRATING: " + (capture.Row.Steering ? "Turn fully left, fully right, then center." : "Fully press and release ONLY this pedal.") + " " + Math.Max(0L, 8 - capture.Clock.ElapsedMilliseconds / 1000) + "s remaining"; } else if (DateTime.UtcNow < noticeUntil) { ((Control)status).Text = notice; } if (buttonCapture != null) { ((Control)buttonCapture.Live).Text = "LISTENING: engage " + buttonCapture.Name + " (" + Math.Max(0, (int)(buttonDeadline - DateTime.UtcNow).TotalSeconds) + "s)"; } if (positionCapture != null) { ((Control)positionCapture.Row.Live).Text = "CAPTURING " + positionCapture.Row.Name + " — hold position; axes=" + string.Join(",", Axes(devices.Resolve(positionCapture.Key))); } if (writeFailures >= 20) { ((Control)status).Text = "Input transport busy — retrying. The game will release stale input."; } ((Control)buttons).Text = "Pressed buttons: " + string.Join(" | ", devices.Items.Select((Device d) => d.Name + ": " + string.Join(",", from b in Enumerable.Range(0, d.Buttons) where SDL.SDL_JoystickGetButton(d.Handle, b) != 0 select b))); } catch (Exception ex4) { enabled.Checked = false; WriteNeutral(); ((Control)status).Text = "Input stopped: " + ex4.Message; } } private void WriteNeutral() { try { Bridge.Write(Path.Combine(root, "input.txt"), Bridge.Line(session, ++sequence, active: false, new double[5], enginePresses, -99, 0)); } catch { } } } internal static class Program { [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hwnd); private static string InstanceMutexName() { string s = Path.GetFullPath(Application.ExecutablePath).ToUpperInvariant(); byte[] array; using (SHA256 sHA = SHA256.Create()) { array = sHA.ComputeHash(Encoding.UTF8.GetBytes(s)); } return "Local\\RVWheelSetup-" + BitConverter.ToString(array).Replace("-", ""); } private static void FocusExistingInstance() { string fullPath = Path.GetFullPath(Application.ExecutablePath); Process[] processesByName = Process.GetProcessesByName("RVWheelSetup"); foreach (Process process in processesByName) { try { if (process.Id == Process.GetCurrentProcess().Id || !Path.GetFullPath(process.MainModule.FileName).Equals(fullPath, StringComparison.OrdinalIgnoreCase)) { continue; } process.Refresh(); if (process.MainWindowHandle != IntPtr.Zero) { SetForegroundWindow(process.MainWindowHandle); } break; } catch { } } } [STAThread] private static int Main(string[] args) { //IL_01f7: Unknown result type (might be due to invalid IL or missing references) if (args.Contains("--self-test")) { Tests.Run(); return 0; } if (args.Contains("--devices")) { try { using Devices devices = new Devices(); foreach (Device item in devices.Items) { Console.WriteLine(string.Concat(item, "\n ", item.Key, "\n SDL force feedback: unavailable")); } try { IList<string> list = DirectInputProbe.ReadAttachedDevices(); Console.WriteLine("DirectInput attached devices:"); foreach (string item2 in list) { Console.WriteLine(" " + item2); } if (list.Count == 0) { Console.WriteLine(" none reported"); } } catch (Exception ex) { Console.WriteLine("DirectInput capability probe unavailable: " + ex.Message); } try { Console.WriteLine(HidPidProbe.ReadSelectedSteeringDevice(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "profile.json"))); } catch (Exception ex2) { Console.WriteLine("HID PID capability probe unavailable: " + ex2.Message); } Console.WriteLine("Devices: " + devices.Items.Count); return 0; } catch (Exception value) { Console.Error.WriteLine(value); return 1; } } bool createdNew = false; using (new Mutex(initiallyOwned: true, InstanceMutexName(), out createdNew)) { if (!createdNew) { FocusExistingInstance(); return 0; } try { using (Devices source = new Devices()) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run((Form)(object)new SetupForm(source)); } return 0; } catch (Exception ex3) { Console.Error.WriteLine(ex3); if (args.Length == 0) { MessageBox.Show(ex3.Message, "RV Wheel Support"); } return 1; } } } } internal static class Tests { private static void Equal(double expected, double actual) { if (Math.Abs(expected - actual) > 1E-05) { throw new Exception("Expected " + expected + ", got " + actual); } } public static void Run() { //IL_030c: Unknown result type (might be due to invalid IL or missing references) Binding binding = new Binding(); binding.DeviceKey = "test"; binding.Minimum = -30000; binding.Center = 1000; binding.Maximum = 31000; binding.Deadzone = 0.0; binding.FirstCaptured = true; binding.CenterCaptured = true; binding.LastCaptured = true; binding.DefaultSteeringRange = false; Binding binding2 = binding; Equal(-1.0, binding2.Normalize(-30000, steering: true)); Equal(0.0, binding2.Normalize(1000, steering: true)); Equal(1.0, binding2.Normalize(31000, steering: true)); Equal(1.0, binding2.Normalize(32767, steering: true)); binding2.Invert = true; Equal(-1.0, binding2.Normalize(31000, steering: true)); binding2.Invert = false; binding2.Deadzone = 0.1; Equal(0.0, binding2.Normalize(2000, steering: true)); binding2.Minimum = 31000; binding2.Maximum = -30000; binding2.Deadzone = 0.0; Equal(-1.0, binding2.Normalize(31000, steering: true)); Equal(1.0, binding2.Normalize(-30000, steering: true)); Equal(0.0, binding2.Normalize(1000, steering: true)); binding2.Minimum = 32767; binding2.Maximum = -32768; binding2.Deadzone = 0.0; Equal(0.0, binding2.Normalize(32767, steering: false)); Equal(1.0, binding2.Normalize(-32768, steering: false)); Equal(0.5, binding2.Normalize(0, steering: false)); binding2.Maximum = binding2.Minimum; Equal(0.0, binding2.Normalize(0, steering: false)); if (binding2.Valid(steering: false)) { throw new Exception("Degenerate calibration accepted"); } binding2.Minimum = -100; binding2.Center = 200; binding2.Maximum = 100; if (binding2.Valid(steering: true)) { throw new Exception("Invalid center accepted"); } binding2.Maximum = 32767; binding2.Minimum = -32768; binding2.FirstCaptured = false; if (binding2.Valid(steering: false)) { throw new Exception("Uncalibrated binding accepted"); } binding2.IsButton = true; binding2.DeviceKey = "test"; Equal(0.0, binding2.Normalize(0, steering: false)); Equal(1.0, binding2.Normalize(1, steering: false)); if (binding2.Valid(steering: true)) { throw new Exception("Button steering accepted"); } using (MemoryStream memoryStream = new MemoryStream(Encoding.UTF8.GetBytes("{\"Version\":1,\"Steering\":{},\"Throttle\":{},\"Brake\":{},\"Handbrake\":{}}"))) { Profile profile = (Profile)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(Profile))).ReadObject((Stream)memoryStream); if (profile.Clutch == null || profile.Engine == null || !profile.Engine.IsButton || profile.Actions == null || profile.Actions.Length != 5 || profile.VisualWheelDegrees != 900 || profile.Version != 4 || profile.ForceFeedbackEnabled || profile.ForceFeedbackStrength != 30 || profile.ForceFeedbackOutputEnabled || !string.IsNullOrEmpty(profile.ForceFeedbackDeviceInstance)) { throw new Exception("Existing profile upgrade failed"); } } string path = Path.Combine(Path.GetTempPath(), "rvw-ffb-test-" + Guid.NewGuid().ToString("N") + ".txt"); try { File.WriteAllText(path, "FFB2 abc 7 -0.375000 18.5 0 0\n"); Equal(-0.375, ForceFeedbackOutput.ReadFreshTorque(path, out var fresh)); if (!fresh) { throw new Exception("Fresh force sample rejected"); } File.WriteAllText(path, "not a force frame\n"); Equal(0.0, ForceFeedbackOutput.ReadFreshTorque(path, out fresh)); if (fresh) { throw new Exception("Malformed force sample accepted"); } File.WriteAllText(path, "FFB2 abc 7 NaN 18.5 0 0\n"); Equal(0.0, ForceFeedbackOutput.ReadFreshTorque(path, out fresh)); if (fresh) { throw new Exception("Non-finite force sample accepted"); } } finally { try { File.Delete(path); } catch { } } Equal(80.0, Marshal.SizeOf(typeof(DirectInputForceFeedback.EffectParameters))); Equal(24.0, Marshal.SizeOf(typeof(DirectInputForceFeedback.DeviceObjectFormat))); Equal(32.0, Marshal.SizeOf(typeof(DirectInputForceFeedback.DeviceDataFormat))); Binding binding3 = new Binding(); binding3.DeviceKey = "wheel"; binding3.Deadzone = 0.0; Binding binding4 = binding3; Equal(-1.0, binding4.Normalize(-32768, steering: true)); Equal(0.0, binding4.Normalize(0, steering: true)); Equal(1.0, binding4.Normalize(32767, steering: true)); string text = Bridge.Line("abc", 42L, active: true, new double[5] { -0.5, 0.25, 1.0, 0.0, -1.0 }, 3L, 5, 7); if (text != "RVW6 abc 42 1 -0.500000 0.250000 1.000000 0.000000 -1.000000 3 5 7 0 0 0 0,0,0,0,0 900 0.000000 0 30\n") { throw new Exception("Protocol culture mismatch"); } binding4.Deadzone = 0.2; double expected = binding4.Normalize(16384, steering: true); Equal(0.500015259254738, binding4.VisualPosition(16384)); double actual = binding4.VisualPosition(16384) * 900.0 / 2.0; Equal(225.00686666, actual); Equal(expected, binding4.Normalize(16384, steering: true)); if (Calibration.ChooseAxis(new int[2] { 0, 10 }, new int[2] { 1023, 11 }, new int[2] { 512, 10 }, steering: true) != 0) { throw new Exception("10-bit steering rejected"); } int[] low = new int[1]; if (Calibration.ChooseAxis(low, new int[1] { 255 }, new int[1] { 128 }, steering: true) != 0) { throw new Exception("8-bit steering rejected"); } int[] low2 = new int[1]; int[] high = new int[1] { 1023 }; int[] center = new int[1]; if (Calibration.ChooseAxis(low2, high, center, steering: true) != -1) { throw new Exception("One-sided steering accepted"); } if (Calibration.ChooseAxis(new int[1] { 100 }, new int[1] { 104 }, new int[1] { 102 }, steering: true) != -1) { throw new Exception("Noise accepted as steering"); } int[] low3 = new int[1]; if (Calibration.ChooseAxis(low3, new int[1] { 255 }, new int[1] { 255 }, steering: false) != 0) { throw new Exception("Reversed low-resolution pedal rejected"); } Binding binding5 = new Binding(); binding5.PositionPattern = true; binding5.PatternAxes = new int[2] { -32768, -32768 }; binding5.PatternTolerance = new int[2] { 4, 4 }; binding5.PatternButtons = new int[1] { 7 }; Binding binding6 = binding5; if (!GearPattern.Matches(binding6, new int[2] { -32768, -32768 }, new int[1] { 7 }, new int[2] { 7, 8 })) { throw new Exception("Position pattern rejected"); } if (GearPattern.Matches(binding6, new int[2] { 0, -32768 }, new int[1] { 7 }, new int[2] { 7, 8 })) { throw new Exception("Same top-row button merged two gear columns"); } if (GearPattern.Matches(binding6, new int[2] { -32768, 32767 }, new int[1] { 8 }, new int[2] { 7, 8 })) { throw new Exception("Top/bottom positions merged"); } if (!GearPattern.Matches(binding6, new int[2] { -32765, -32768 }, new int[2] { 7, 9 }, new int[2] { 7, 8 })) { throw new Exception("Noise or unrelated button broke position recognition"); } if (GearPattern.Matches(binding6, new int[2] { -32768, -32768 }, new int[2] { 7, 8 }, new int[2] { 7, 8 })) { throw new Exception("Conflicting row signals accepted"); } Binding binding7 = new Binding(); binding7.PositionPattern = true; int[] patternAxes = new int[1]; binding7.PatternAxes = patternAxes; binding7.PatternTolerance = new int[1] { 4 }; binding7.PatternButtons = new int[2] { 0, 7 }; Binding binding8 = binding7; int[] axes = new int[1]; if (GearPattern.Matches(binding8, axes, new int[2] { 1, 7 }, new int[3] { 0, 1, 7 })) { throw new Exception("Different button chords merged"); } Binding binding9 = new Binding(); binding9.PositionPattern = true; int[] patternAxes2 = new int[2]; binding9.PatternAxes = patternAxes2; binding9.PatternTolerance = new int[2] { 4, 4 }; binding9.PatternButtons = new int[1] { 7 }; binding9.PatternHats = new int[1] { 8 }; Binding binding10 = binding9; int[] axes2 = new int[2]; if (!GearPattern.Matches(binding10, axes2, new int[1] { 7 }, new int[2] { 7, 8 }, new int[1] { 8 })) { throw new Exception("POV gear not recognized"); } axes2 = new int[2]; if (GearPattern.Matches(binding10, axes2, new int[1] { 7 }, new int[2] { 7, 8 }, new int[1] { 2 })) { throw new Exception("POV columns collapsed into same top row"); } Console.WriteLine("PASS: steering, asymmetric center, clamps, inversion, deadzone, reversed pedals, invalid calibration and wire format."); } } internal sealed class ForceFeedbackDevice { public Guid InstanceGuid; public Guid ProductGuid; public string Name; public bool ConstantForce; public override string ToString() { string text = Name; string text2 = ProductGuid.ToString("N").Substring(0, 8).ToUpperInvariant(); if (string.IsNullOrWhiteSpace(text) || text.Length < 3) { text = ((text2 == "03013416") ? "CAMMUS C5 Wheel Base" : ("DirectInput device " + text2)); } return text + " [" + InstanceGuid.ToString("N").Substring(0, 8) + "]"; } } internal static class DirectInputProbe { [StructLayout(LayoutKind.Sequential, Pack = 4)] private struct DeviceCaps { public uint Size; public uint Flags; public uint DeviceType; public uint Axes; public uint Buttons; public uint Povs; public uint ForceFeedbackSamplePeriod; public uint ForceFeedbackMinTimeResolution; public uint FirmwareRevision; public uint HardwareRevision; public uint ForceFeedbackDriverVersion; } [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int EnumDevicesCallback(IntPtr instance, IntPtr context); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int EnumDevicesFn(IntPtr self, uint deviceType, EnumDevicesCallback callback, IntPtr context, uint flags); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int CreateDeviceFn(IntPtr self, ref Guid instance, out IntPtr device, IntPtr outer); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int GetCapabilitiesFn(IntPtr self, ref DeviceCaps caps); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int EnumEffectsFn(IntPtr self, EnumDevicesCallback callback, IntPtr context, uint flags); private const uint Version = 2048u; private const uint AttachedOnly = 1u; private const uint ForceFeedbackFlag = 256u; private const uint ConstantForceEffect = 1u; private static readonly Guid IID_DirectInput8A = new Guid("BF798031-483A-4DA2-AA99-5D64ED369700"); [DllImport("dinput8.dll", CallingConvention = CallingConvention.StdCall)] private static extern int DirectInput8Create(IntPtr instance, uint version, ref Guid iid, out IntPtr directInput, IntPtr outer); public static List<ForceFeedbackDevice> EnumerateForceFeedbackDevices() { List<ForceFeedbackDevice> result = new List<ForceFeedbackDevice>(); IntPtr directInput = IntPtr.Zero; Guid iid = IID_DirectInput8A; int num = DirectInput8Create(ProcessInstance(), 2048u, ref iid, out directInput, IntPtr.Zero); if (num < 0 || directInput == IntPtr.Zero) { throw new InvalidOperationException("DirectInput8Create failed: 0x" + num.ToString("X8")); } try { CreateDeviceFn create = (CreateDeviceFn)Marshal.GetDelegateForFunctionPointer(Method(directInput, 3), typeof(CreateDeviceFn)); EnumDevicesFn enumDevicesFn = (EnumDevicesFn)Marshal.GetDelegateForFunctionPointer(Method(directInput, 4), typeof(EnumDevicesFn)); EnumDevicesCallback enumDevicesCallback = delegate(IntPtr instancePtr, IntPtr context) { IntPtr device = IntPtr.Zero; try { Guid instance = (Guid)Marshal.PtrToStructure(IntPtr.Add(instancePtr, 4), typeof(Guid)); Guid productGuid = (Guid)Marshal.PtrToStructure(IntPtr.Add(instancePtr, 20), typeof(Guid)); int num3 = create(directInput, ref instance, out device, IntPtr.Zero); if (num3 < 0 || device == IntPtr.Zero) { return 1; } DeviceCaps caps = new DeviceCaps { Size = (uint)Marshal.SizeOf(typeof(DeviceCaps)) }; GetCapabilitiesFn getCapabilitiesFn = (GetCapabilitiesFn)Marshal.GetDelegateForFunctionPointer(Method(device, 3), typeof(GetCapabilitiesFn)); int num4 = getCapabilitiesFn(device, ref caps); if (num4 < 0 || (caps.Flags & 0x100) == 0) { return 1; } bool constantForce = false; EnumDevicesCallback enumDevicesCallback2 = delegate(IntPtr effectPtr, IntPtr effectContext) { uint num6 = (uint)Marshal.ReadInt32(effectPtr, 20); if ((num6 & 0xFF) == 1) { constantForce = true; } return 1; }; EnumEffectsFn enumEffectsFn = (EnumEffectsFn)Marshal.GetDelegateForFunctionPointer(Method(device, 19), typeof(EnumEffectsFn)); int num5 = enumEffectsFn(device, enumDevicesCallback2, IntPtr.Zero, 1u); GC.KeepAlive(enumDevicesCallback2); if (num5 >= 0 && constantForce) { string text = Marshal.PtrToStringAnsi(IntPtr.Add(instancePtr, 40)); if (string.IsNullOrWhiteSpace(text)) { text = productGuid.ToString(); } result.Add(new ForceFeedbackDevice { InstanceGuid = instance, ProductGuid = productGuid, Name = text, ConstantForce = true }); } return 1; } catch { return 1; } finally { if (device != IntPtr.Zero) { Marshal.Release(device); } } }; int num2 = enumDevicesFn(directInput, 0u, enumDevicesCallback, IntPtr.Zero, 1u); GC.KeepAlive(enumDevicesCallback); if (num2 < 0) { throw new InvalidOperationException("DirectInput EnumDevices failed: 0x" + num2.ToString("X8")); } } finally { Marshal.Release(directInput); } return (from d in result group d by d.InstanceGuid into g select g.First()).ToList(); } private static IntPtr Method(IntPtr comObject, int slot) { IntPtr ptr = Marshal.ReadIntPtr(comObject); return Marshal.ReadIntPtr(ptr, slot * IntPtr.Size); } public static IList<string> ReadAttachedDevices() { List<string> result = new List<string>(); IntPtr directInput = IntPtr.Zero; Guid iid = IID_DirectInput8A; int num = DirectInput8Create(ProcessInstance(), 2048u, ref iid, out directInput, IntPtr.Zero); if (num < 0 || directInput == IntPtr.Zero) { throw new InvalidOperationException("DirectInput8Create failed: 0x" + num.ToString("X8")); } try { CreateDeviceFn create = (CreateDeviceFn)Marshal.GetDelegateForFunctionPointer(Method(directInput, 3), typeof(CreateDeviceFn)); EnumDevicesFn enumDevicesFn = (EnumDevicesFn)Marshal.GetDelegateForFunctionPointer(Method(directInput, 4), typeof(EnumDevicesFn)); EnumDevicesCallback enumDevicesCallback = delegate(IntPtr instancePtr, IntPtr context) { IntPtr device = IntPtr.Zero; try { Guid instance = (Guid)Marshal.PtrToStructure(IntPtr.Add(instancePtr, 4), typeof(Guid)); Guid guid = (Guid)Marshal.PtrToStructure(IntPtr.Add(instancePtr, 20), typeof(Guid)); int num3 = create(directInput, ref instance, out device, IntPtr.Zero); if (num3 < 0 || device == IntPtr.Zero) { result.Add(string.Concat("Product ", guid, " | DirectInput device open failed: 0x", num3.ToString("X8"))); return 1; } DeviceCaps caps = new DeviceCaps { Size = (uint)Marshal.SizeOf(typeof(DeviceCaps)) }; GetCapabilitiesFn getCapabilitiesFn = (GetCapabilitiesFn)Marshal.GetDelegateForFunctionPointer(Method(device, 3), typeof(GetCapabilitiesFn)); int num4 = getCapabilitiesFn(device, ref caps); bool flag = num4 >= 0 && (caps.Flags & 0x100) != 0; bool constantForce = false; EnumDevicesCallback enumDevicesCallback2 = delegate(IntPtr effectPtr, IntPtr effectContext) { uint num6 = (uint)Marshal.ReadInt32(effectPtr, 20); if ((num6 & 0xFF) == 1) { constantForce = true; } return 1; }; EnumEffectsFn enumEffectsFn = (EnumEffectsFn)Marshal.GetDelegateForFunctionPointer(Method(device, 19), typeof(EnumEffectsFn)); int num5 = enumEffectsFn(device, enumDevicesCallback2, IntPtr.Zero, 1u); GC.KeepAlive(enumDevicesCallback2); result.Add(string.Concat("Product ", guid, " | instance ", instance, " | DirectInput FFB: ", flag ? "reported" : "not reported", (num4 < 0) ? (" (capability query failed: 0x" + num4.ToString("X8") + ")") : "", (num5 < 0) ? (" | constant-force effect query failed: 0x" + num5.ToString("X8")) : (" | constant-force effect: " + (constantForce ? "reported" : "not reported")))); return 1; } catch (Exception ex) { result.Add("DirectInput device query error: " + ex.GetType().Name + ": " + ex.Message); return 1; } finally { if (device != IntPtr.Zero) { Marshal.Release(device); } } }; int num2 = enumDevicesFn(directInput, 0u, enumDevicesCallback, IntPtr.Zero, 1u); GC.KeepAlive(enumDevicesCallback); if (num2 < 0) { throw new InvalidOperationException("DirectInput EnumDevices failed: 0x" + num2.ToString("X8")); } } finally { Marshal.Release(directInput); } return result; } private static IntPtr ProcessInstance() { return Marshal.GetHINSTANCE(typeof(DirectInputProbe).Module); } } internal static class ForceFeedbackOutput { public static double ReadFreshTorque(string path, out bool fresh) { fresh = false; if (!File.Exists(path) || (DateTime.UtcNow - File.GetLastWriteTimeUtc(path)).TotalMilliseconds > 200.0) { return 0.0; } string text; using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) { using StreamReader streamReader = new StreamReader(stream); text = streamReader.ReadLine(); } if (string.IsNullOrWhiteSpace(text)) { return 0.0; } string[] array = text.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 4 || array[0] != "FFB2" || !double.TryParse(array[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || double.IsNaN(result) || double.IsInfinity(result)) { return 0.0; } fresh = true; return Math.Max(-1.0, Math.Min(1.0, result)); } } internal sealed class DirectInputForceFeedback : IDisposable { private struct ConstantForce { public int Magnitude; } public struct EffectParameters { public uint Size; public uint Flags; public uint Duration; public uint SamplePeriod; public uint Gain; public uint TriggerButton; public uint TriggerRepeatInterval; public uint AxesCount; public IntPtr Axes; public IntPtr Direction; public IntPtr Envelope; public uint TypeSpecificSize; public IntPtr TypeSpecific; public uint StartDelay; } public struct DeviceObjectFormat { public IntPtr Guid; public uint Offset; public uint Type; public uint Flags; } public struct DeviceDataFormat { public uint Size; public uint ObjectSize; public uint Flags; public uint DataSize; public uint ObjectCount; public IntPtr Objects; } [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int EnumCallback(IntPtr instance, IntPtr context); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int CreateDeviceFn(IntPtr self, ref Guid instance, out IntPtr device, IntPtr outer); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int SetCooperativeLevelFn(IntPtr self, IntPtr hwnd, uint flags); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int SetDataFormatFn(IntPtr self, ref DeviceDataFormat format); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int DeviceActionFn(IntPtr self); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int EnumObjectsFn(IntPtr self, EnumCallback callback, IntPtr context, uint flags); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int CreateEffectFn(IntPtr self, ref Guid effectGuid, ref EffectParameters parameters, out IntPtr effect, IntPtr outer); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int SetEffectParametersFn(IntPtr self, ref EffectParameters parameters, uint flags); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int StartEffectFn(IntPtr self, uint iterations, uint flags); [UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int StopEffectFn(IntPtr self); private const uint DirectInputVersion = 2048u; private const uint CooperativeExclusiveBackground = 9u; private const uint DieffObjectIds = 1u; private const uint DieffCartesian = 16u; private const uint DiepTypeSpecificParams = 256u; private const uint Infinite = uint.MaxValue; private static readonly Guid IID_DirectInput8A = new Guid("BF798031-483A-4DA2-AA99-5D64ED369700"); private static readonly Guid GuidConstantForce = new Guid("13541C20-8E33-11D0-9AD0-00A0C9A06E35"); private static readonly Guid GuidXAxis = new Guid("A36D02E0-C9F3-11CF-BFC7-444553540000"); private IntPtr directInput = IntPtr.Zero; private IntPtr device = IntPtr.Zero; private IntPtr effect = IntPtr.Zero; private string openInstanceGuid = ""; private DateTime retryAfter = DateTime.MinValue; private string lastError = ""; private bool started; [DllImport("dinput8.dll", CallingConvention = CallingConvention.StdCall)] private static extern int DirectInput8Create(IntPtr instance, uint version, ref Guid iid, out IntPtr directInput, IntPtr outer); private static IntPtr Method(IntPtr obj, int slot) { return Marshal.ReadIntPtr(Marshal.ReadIntPtr(obj), slot * IntPtr.Size); } private static T Fn<T>(IntPtr obj, int slot) where T : class { return Marshal.GetDelegateForFunctionPointer(Method(obj, slot), typeof(T)) as T; } private static IntPtr Instance() { return Marshal.GetHINSTANCE(typeof(DirectInputForceFeedback).Module); } public string Update(IntPtr hwnd, string selectedInstanceGuid, bool requested, double normalizedTorque) { if (!requested) { CloseDevice(); return "Output armed; waiting for focused game and fresh physics"; } if (string.IsNullOrWhiteSpace(selectedInstanceGuid)) { CloseDevice(); return "Choose a force feedback device from the list"; } if (device != IntPtr.Zero && !string.Equals(openInstanceGuid, selectedInstanceGuid, StringComparison.OrdinalIgnoreCase)) { CloseDevice(); } if (device == IntPtr.Zero) { if (DateTime.UtcNow < retryAfter) { if (lastError.Length != 0) { return "Output unavailable: " + lastError; } return "Connecting to selected DirectInput device"; } try { Open(hwnd, selectedInstanceGuid); lastError = ""; } catch (Exception ex) { lastError = ex.Message; retryAfter = DateTime.UtcNow.AddSeconds(2.0); CloseDevice(); return "Output unavailable: " + lastError; } } try { int num = (int)Math.Round(Math.Max(-0.3, Math.Min(0.3, normalizedTorque)) * 10000.0); SetMagnitude(num); return "DirectInput updated force=" + num + "/10000 (30% cap)"; } catch (Exception ex2) { lastError = ex2.Message; retryAfter = DateTime.UtcNow.AddSeconds(2.0); CloseDevice(); return "Output stopped: " + lastError; } } private void Open(IntPtr hwnd, string selectedInstanceGuid) { Guid iid = IID_DirectInput8A; int hr = DirectInput8Create(Instance(), 2048u, ref iid, out directInput, IntPtr.Zero); Check(hr, "DirectInput8Create"); if (!Guid.TryParse(selectedInstanceGuid, out var result)) { throw new InvalidOperationException("Invalid force feedback device selection"); } Check(Fn<CreateDeviceFn>(directInput, 3)(directInput, ref result, out device, IntPtr.Zero), "Open selected DirectInput device"); if (device == IntPtr.Zero) { throw new InvalidOperationException("Selected DirectInput device returned no handle"); } Check(Fn<SetCooperativeLevelFn>(device, 13)(device, hwnd, 9u), "Set device cooperative level"); uint firstAxis = 0u; uint xAxis = 0u; uint firstActuator = 0u; uint xActuator = 0u; Guid firstAxisGuid = Guid.Empty; Guid xAxisGuid = Guid.Empty; Guid firstActuatorGuid = Guid.Empty; Guid xActuatorGuid = Guid.Empty; EnumCallback enumCallback = delegate(IntPtr obj, IntPtr context) { Guid guid2 = (Guid)Marshal.PtrToStructure(IntPtr.Add(obj, 4), typeof(Guid)); uint num2 = (uint)Marshal.ReadInt32(obj, 24); uint num3 = (uint)Marshal.ReadInt32(obj, 28); if (firstAxis == 0) { firstAxis = num2; firstAxisGuid = guid2; } if (guid2 == GuidXAxis) { xAxis = num2; xAxisGuid = guid2; } if ((num3 & 1) != 0) { if (firstActuator == 0) { firstActuator = num2; firstActuatorGuid = gui