using SharpDX.DirectInput; using System; using System.Collections.Generic; using System.ComponentModel; using System.Configuration; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Telerik.WinControls; using Telerik.WinControls.UI; using Timer = System.Timers.Timer; namespace PJ64Savestater { public partial class MainForm : Telerik.WinControls.UI.RadForm { private const string FILTER_INPUTPROFILE = "Json files (*.json)|*.json"; private const string PROFILE_CODE = "Project64 Savestater"; private const string DEFAULT_PROFILE_FILENAME = "Profile.json"; private Timer myTimer; private DirectInput DInput = new DirectInput(); private DeviceInstance[] allDevices = Array.Empty(); private Joystick curPad = null; private RadTextBox focuesTextBox = null; private InputProfile curProfile = null; private bool enableActionExecution = false; private int toleranceOffset; public MainForm() { // Read configs var reader = new AppSettingsReader(); toleranceOffset = (int)reader.GetValue("toleranceOffset", typeof(int)); // Init components InitializeComponent(); // Init Timer myTimer = new Timer(1) { SynchronizingObject = this, AutoReset = true, Enabled = true }; myTimer.Elapsed += MyTimer_Elapsed; } private void LoadPads() { allDevices = DInput.GetDevices(DeviceClass.GameControl, DeviceEnumerationFlags.AttachedOnly).ToArray(); radDropDownList_Pads.Items.Clear(); foreach (DeviceInstance d in allDevices) radDropDownList_Pads.Items.Add(d.InstanceName); } private void SetCurrentPad(Guid guid) { curPad = new Joystick(DInput, guid); foreach (DeviceObjectInstance doi in curPad.GetObjects(DeviceObjectTypeFlags.Axis)) curPad.GetObjectPropertiesById(doi.ObjectId).Range = new InputRange(-5000, 5000); curPad.Properties.AxisMode = DeviceAxisMode.Absolute; curPad.SetCooperativeLevel(Handle, CooperativeLevel.NonExclusive | CooperativeLevel.Background); curPad.Acquire(); if (curProfile is object) curProfile.ControllerInstanceGuid = guid; } private static string GetDefaultProfileFilePath() { var dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), Application.ProductName); Directory.CreateDirectory(dir); return Path.Combine(dir, DEFAULT_PROFILE_FILENAME); } private void CheckForInput() { if (curPad is object) { var inputCodes = new List(); var state = new JoystickState(); curPad.Poll(); curPad.GetCurrentState(ref state); for (int i = 0, loopTo = state.Buttons.Length - 1; i <= loopTo; i++) { if (state.Buttons[i]) inputCodes.Add("Button " + i); } for (int i = 0, loopTo1 = state.PointOfViewControllers.Length - 1; i <= loopTo1; i++) { int val = state.PointOfViewControllers[i]; if (val > -1) inputCodes.Add("Point " + i + " " + val); } for (int i = 0, loopTo2 = state.Sliders.Length - 1; i <= loopTo2; i++) { int val = state.Sliders[i]; if (val > toleranceOffset) inputCodes.Add("Slider " + i + " +"); if (val < -toleranceOffset) inputCodes.Add("Slider " + i + " -"); } for (int i = 0, loopTo3 = state.AccelerationSliders.Length - 1; i <= loopTo3; i++) { int val = state.AccelerationSliders[i]; if (val > toleranceOffset) inputCodes.Add("ASlider " + i + " +"); if (val < -toleranceOffset) inputCodes.Add("ASlider " + i + " -"); } for (int i = 0, loopTo4 = state.ForceSliders.Length - 1; i <= loopTo4; i++) { int val = state.ForceSliders[i]; if (val > toleranceOffset) inputCodes.Add("FSlider " + i + " +"); if (val < -toleranceOffset) inputCodes.Add("FSlider " + i + " -"); } for (int i = 0, loopTo5 = state.VelocitySliders.Length - 1; i <= loopTo5; i++) { int val = state.VelocitySliders[i]; if (val > toleranceOffset) inputCodes.Add("VSlider " + i + " +"); if (val < -toleranceOffset) inputCodes.Add("VSlider " + i + " -"); } if (state.X > toleranceOffset) inputCodes.Add("X +"); if (state.X < -toleranceOffset) inputCodes.Add("X -"); if (state.Y > toleranceOffset) inputCodes.Add("Y +"); if (state.Y < -toleranceOffset) inputCodes.Add("Y -"); if (state.Z > toleranceOffset) inputCodes.Add("Z +"); if (state.Z < -toleranceOffset) inputCodes.Add("Z -"); if (state.AccelerationX > toleranceOffset) inputCodes.Add("AX +"); if (state.AccelerationX < -toleranceOffset) inputCodes.Add("AX -"); if (state.AccelerationY > toleranceOffset) inputCodes.Add("AY +"); if (state.AccelerationY < -toleranceOffset) inputCodes.Add("AY -"); if (state.AccelerationZ > toleranceOffset) inputCodes.Add("AZ +"); if (state.AccelerationZ < -toleranceOffset) inputCodes.Add("AZ -"); if (state.AngularAccelerationX > toleranceOffset) inputCodes.Add("AAX +"); if (state.AngularAccelerationX < -toleranceOffset) inputCodes.Add("AAX -"); if (state.AngularAccelerationY > toleranceOffset) inputCodes.Add("AAY +"); if (state.AngularAccelerationY < -toleranceOffset) inputCodes.Add("AAY -"); if (state.AngularAccelerationZ > toleranceOffset) inputCodes.Add("AAZ +"); if (state.AngularAccelerationZ < -toleranceOffset) inputCodes.Add("AAZ -"); if (state.ForceX > toleranceOffset) inputCodes.Add("FX +"); if (state.ForceX < -toleranceOffset) inputCodes.Add("FX -"); if (state.ForceY > toleranceOffset) inputCodes.Add("FY +"); if (state.ForceY < -toleranceOffset) inputCodes.Add("FY -"); if (state.ForceZ > toleranceOffset) inputCodes.Add("FZ +"); if (state.ForceZ < -toleranceOffset) inputCodes.Add("FZ -"); if (state.RotationX > toleranceOffset) inputCodes.Add("RX +"); if (state.RotationX < -toleranceOffset) inputCodes.Add("RX -"); if (state.RotationY > toleranceOffset) inputCodes.Add("RY +"); if (state.RotationY < -toleranceOffset) inputCodes.Add("RY -"); if (state.RotationZ > toleranceOffset) inputCodes.Add("RZ +"); if (state.RotationZ < -toleranceOffset) inputCodes.Add("RZ -"); if (state.TorqueX > toleranceOffset) inputCodes.Add("TX +"); if (state.TorqueX < -toleranceOffset) inputCodes.Add("TX -"); if (state.TorqueY > toleranceOffset) inputCodes.Add("TY +"); if (state.TorqueY < -toleranceOffset) inputCodes.Add("TY -"); if (state.TorqueZ > toleranceOffset) inputCodes.Add("TZ +"); if (state.TorqueZ < -toleranceOffset) inputCodes.Add("TZ -"); if (state.VelocityX > toleranceOffset) inputCodes.Add("VX +"); if (state.VelocityX < -toleranceOffset) inputCodes.Add("VX -"); if (state.VelocityY > toleranceOffset) inputCodes.Add("VY +"); if (state.VelocityY < -toleranceOffset) inputCodes.Add("VY -"); if (state.VelocityZ > toleranceOffset) inputCodes.Add("VZ +"); if (state.VelocityZ < -toleranceOffset) inputCodes.Add("VZ -"); if (inputCodes.Any()) { if (focuesTextBox is object) focuesTextBox.Text = inputCodes.Last(); else if (enableActionExecution) CheckForActions(inputCodes.ToArray()); } } } private void CheckForActions(string[] inputCodes) { foreach (var inputCode in inputCodes) { var inputControl = GetInputControlFromString(inputCode); foreach (var ctrl in curProfile.Controls) { if (ctrl.Value == inputControl) ExecuteAction(ctrl.Key); } } } private static void ExecuteAction(string actionCode) { if (actionCode == ActionCodes.CreateSavestate) SendKeys.Send("{F5}"); else if (actionCode == ActionCodes.LoadSavestate) SendKeys.Send("{F7}"); } private void SetAction(string actionCode, string inputCode) { if (curProfile is object) curProfile[actionCode] = GetInputControlFromString(inputCode); } public static InputControl GetInputControlFromString(string str) { var input = new InputControl(); var p = str.Split(' '); var switchExpr = p[0]; switch (switchExpr) { case "Button": input.InputKey = InputKeys.Buttons; input.KeyIndex = Convert.ToInt32(p[1]); break; case "Point": input.InputKey = InputKeys.PointOfViewControllers; input.KeyIndex = Convert.ToInt32(p[1]); input.Value = p[2]; break; case "Slider": input.InputKey = InputKeys.Sliders; input.KeyIndex = Convert.ToInt32(p[1]); break; case "ASlider": input.InputKey = InputKeys.AccelerationSliders; input.KeyIndex = Convert.ToInt32(p[1]); break; case "FSlider": input.InputKey = InputKeys.ForceSliders; input.KeyIndex = Convert.ToInt32(p[1]); break; case "VSlider": input.InputKey = InputKeys.VelocitySliders; input.KeyIndex = Convert.ToInt32(p[1]); break; case "X": input.InputKey = InputKeys.X; input.Value = p[1]; break; case "Y": input.InputKey = InputKeys.Y; input.Value = p[1]; break; case "Z": input.InputKey = InputKeys.Z; input.Value = p[1]; break; case "AX": input.InputKey = InputKeys.AccelerationX; input.Value = p[1]; break; case "AY": input.InputKey = InputKeys.AccelerationY; input.Value = p[1]; break; case "AZ": input.InputKey = InputKeys.AccelerationZ; input.Value = p[1]; break; case "AAX": input.InputKey = InputKeys.AngularAccelerationX; input.Value = p[1]; break; case "AAY": input.InputKey = InputKeys.AngularAccelerationY; input.Value = p[1]; break; case "AAZ": input.InputKey = InputKeys.AngularAccelerationZ; input.Value = p[1]; break; case "FX": input.InputKey = InputKeys.ForceX; input.Value = p[1]; break; case "FY": input.InputKey = InputKeys.ForceY; input.Value = p[1]; break; case "FZ": input.InputKey = InputKeys.ForceZ; input.Value = p[1]; break; case "RX": input.InputKey = InputKeys.RotationX; input.Value = p[1]; break; case "RY": input.InputKey = InputKeys.RotationY; input.Value = p[1]; break; case "RZ": input.InputKey = InputKeys.RotationZ; input.Value = p[1]; break; case "TX": input.InputKey = InputKeys.TorqueX; input.Value = p[1]; break; case "TY": input.InputKey = InputKeys.TorqueY; input.Value = p[1]; break; case "TZ": input.InputKey = InputKeys.TorqueZ; input.Value = p[1]; break; case "VX": input.InputKey = InputKeys.VelocityX; input.Value = p[1]; break; case "VY": input.InputKey = InputKeys.VelocityY; input.Value = p[1]; break; case "VZ": input.InputKey = InputKeys.VelocityZ; input.Value = p[1]; break; } return input; } public static string GetStringFromInputControl(InputControl input) { string str = string.Empty; if (input.InputKey is null && input.KeyIndex is null && input.Value is null) return string.Empty; switch (input.InputKey) { case var @case when @case == InputKeys.Buttons: { return $"Button {input.KeyIndex}"; } case var case1 when case1 == InputKeys.PointOfViewControllers: { return $"Point {input.KeyIndex} {input.Value}"; } case var case2 when case2 == InputKeys.Sliders: { return $"Slider {input.KeyIndex}"; } case var case3 when case3 == InputKeys.AccelerationSliders: { return $"ASlider {input.KeyIndex}"; } case var case4 when case4 == InputKeys.ForceSliders: { return $"FSlider {input.KeyIndex}"; } case var case5 when case5 == InputKeys.VelocitySliders: { return $"VSlider {input.KeyIndex}"; } case var case6 when case6 == InputKeys.X: { return $"X {input.Value}"; } case var case7 when case7 == InputKeys.Y: { return $"Y {input.Value}"; } case var case8 when case8 == InputKeys.Z: { return $"Z {input.Value}"; } case var case9 when case9 == InputKeys.AccelerationX: { return $"AX {input.Value}"; } case var case10 when case10 == InputKeys.AccelerationY: { return $"AY {input.Value}"; } case var case11 when case11 == InputKeys.AccelerationZ: { return $"AZ {input.Value}"; } case var case12 when case12 == InputKeys.AngularAccelerationX: { return $"AAX {input.Value}"; } case var case13 when case13 == InputKeys.AngularAccelerationY: { return $"AAY {input.Value}"; } case var case14 when case14 == InputKeys.AngularAccelerationZ: { return $"AAZ {input.Value}"; } case var case15 when case15 == InputKeys.ForceX: { return $"FX {input.Value}"; } case var case16 when case16 == InputKeys.ForceY: { return $"FY {input.Value}"; } case var case17 when case17 == InputKeys.ForceZ: { return $"FZ {input.Value}"; } case var case18 when case18 == InputKeys.RotationX: { return $"RX {input.Value}"; } case var case19 when case19 == InputKeys.RotationY: { return $"RY {input.Value}"; } case var case20 when case20 == InputKeys.RotationZ: { return $"RZ {input.Value}"; } case var case21 when case21 == InputKeys.TorqueX: { return $"TX {input.Value}"; } case var case22 when case22 == InputKeys.TorqueY: { return $"TY {input.Value}"; } case var case23 when case23 == InputKeys.TorqueZ: { return $"TZ {input.Value}"; } case var case24 when case24 == InputKeys.VelocityX: { return $"VX {input.Value}"; } case var case25 when case25 == InputKeys.VelocityY: { return $"VY {input.Value}"; } case var case26 when case26 == InputKeys.VelocityZ: { return $"VZ {input.Value}"; } } return str; } private void LoadProfile(string filePath) { var p = !string.IsNullOrEmpty(filePath) && System.IO.File.Exists(filePath) ? InputProfile.Load(filePath) : new InputProfile(PROFILE_CODE); if (p.ProfileCode == PROFILE_CODE) { radTextBox_CreateSavestate.Text = GetStringFromInputControl(p[ActionCodes.CreateSavestate]); radTextBox_LoadSavestate.Text = GetStringFromInputControl(p[ActionCodes.LoadSavestate]); try { var tguid = allDevices.FirstOrDefault(n => Globals.CompareTwoByteArrays(n.InstanceGuid.ToByteArray(), p.ControllerInstanceGuid.ToByteArray())); if (tguid is object) radDropDownList_Pads.SelectedIndex = Array.IndexOf(allDevices, tguid); } catch (Exception) { } curProfile = p; } else RadMessageBox.Show(this, LangRes.MsgBox_WrongProfileCode, LangRes.MsgBox_WrongProfileCode_Title, MessageBoxButtons.OK, RadMessageIcon.Exclamation); } private void MyTimer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { CheckForInput(); } private void MainForm_Shown(object sender, EventArgs e) { LoadPads(); LoadProfile(GetDefaultProfileFilePath()); } private void radButton_StartStopListening_Click(object sender, EventArgs e) { if (enableActionExecution) { radButton_StartStopListening.Image = Properties.Resources.icons8_play_button_circled_16px; radButton_StartStopListening.Text = LangRes.Button_StartListening; enableActionExecution = false; } else { radButton_StartStopListening.Image = Properties.Resources.icons8_stop_squared_16px; radButton_StartStopListening.Text = LangRes.Button_StopListening; enableActionExecution = true; } } private void radButton3_Click(object sender, EventArgs e) { var sfd_SaveInputProfile = new SaveFileDialog { Filter = FILTER_INPUTPROFILE }; if (sfd_SaveInputProfile.ShowDialog(this) == DialogResult.OK) curProfile.Save(sfd_SaveInputProfile.FileName); } private void radButton4_Click(object sender, EventArgs e) { var ofd_SaveInputProfile = new OpenFileDialog { Filter = FILTER_INPUTPROFILE }; if (ofd_SaveInputProfile.ShowDialog(this) == DialogResult.OK) LoadProfile(ofd_SaveInputProfile.FileName); } private void radTextBox1_TextChanged(object sender, EventArgs e) { SetAction(ActionCodes.LoadSavestate, ((RadTextBox)sender).Text); } private void radTextBox2_TextChanged(object sender, EventArgs e) { SetAction(ActionCodes.CreateSavestate, ((RadTextBox)sender).Text); } private void radButton1_Click(object sender, EventArgs e) { LoadPads(); } private void radDropDownList1_SelectedIndexChanged(object sender, Telerik.WinControls.UI.Data.PositionChangedEventArgs e) { int index = radDropDownList_Pads.SelectedIndex; if (index >= 0) SetCurrentPad(allDevices[index].InstanceGuid); } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { curProfile.Save(GetDefaultProfileFilePath()); } private void RadTextBox_Savestate_Clicked(object sender, EventArgs e) { if (curPad is object && curProfile is object) focuesTextBox = (RadTextBox)sender; } private void RadTextBox_Savestate_LostFocus(object sender, EventArgs e) { focuesTextBox = null; } } }