Decompiled source of RVWheelSupport v0.2.2

shimloader/mod/RVWheelSupport/Companion/RVWheelSetup.exe

Decompiled a week ago
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