diff --git a/fpPS4.lpi b/fpPS4.lpi
index 525be1d5..7993a06e 100644
--- a/fpPS4.lpi
+++ b/fpPS4.lpi
@@ -31,7 +31,7 @@
-
+
@@ -513,21 +513,42 @@
-
+
+
+
+
+
-
+
+
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/inputs/formbindbutton.lfm b/src/inputs/formbindbutton.lfm
new file mode 100644
index 00000000..853d32a9
--- /dev/null
+++ b/src/inputs/formbindbutton.lfm
@@ -0,0 +1,43 @@
+object FormBindButtons: TFormBindButtons
+ Left = 701
+ Height = 401
+ Top = 433
+ Width = 929
+ Caption = 'Button Binding'
+ ClientHeight = 401
+ ClientWidth = 929
+ DesignTimePPI = 192
+ OnShow = FormShow
+ LCLVersion = '2.3.0.0'
+ object BitBtn1: TBitBtn
+ Left = 388
+ Height = 60
+ Top = 272
+ Width = 150
+ Caption = 'Cancel'
+ OnClick = BitBtn1Click
+ TabOrder = 0
+ end
+ object BitBtn2: TBitBtn
+ Left = 328
+ Height = 60
+ Top = 176
+ Width = 272
+ Caption = 'Unbind this button'
+ OnClick = BitBtn2Click
+ TabOrder = 1
+ end
+ object Label1: TLabel
+ Left = 208
+ Height = 32
+ Top = 72
+ Width = 526
+ Caption = 'Press a button or move a joystick / analog trigger'
+ end
+ object CheckInput: TTimer
+ Interval = 100
+ OnTimer = CheckInputTimer
+ Left = 736
+ Top = 216
+ end
+end
diff --git a/src/inputs/formbindbutton.pas b/src/inputs/formbindbutton.pas
new file mode 100644
index 00000000..80260e44
--- /dev/null
+++ b/src/inputs/formbindbutton.pas
@@ -0,0 +1,76 @@
+unit formBindButton;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Buttons, StdCtrls,
+ ExtCtrls, uMappableInputs, Windows;
+
+type
+
+ { TFormBindButtons }
+
+ TFormBindButtons = class(TForm)
+ BitBtn1: TBitBtn;
+ BitBtn2: TBitBtn;
+ CheckInput: TTimer;
+ Label1: TLabel;
+ procedure BitBtn1Click(Sender: TObject);
+ procedure BitBtn2Click(Sender: TObject);
+ procedure CheckInputTimer(Sender: TObject);
+ procedure FormShow(Sender: TObject);
+ private
+
+ public
+ XInputKey: EnumXInputButtons;
+ ResetToDefault: boolean;
+ end;
+
+var
+ FormBindButtons: TFormBindButtons;
+
+implementation
+
+uses XInput;
+
+{$R *.lfm}
+
+{ TFormBindButtons }
+
+procedure TFormBindButtons.CheckInputTimer(Sender: TObject);
+var
+ i: integer;
+ state: TXInputState;
+ key: EnumXInputButtons;
+begin
+ ZeroMemory(@state, SizeOf(TXInputState));
+ if XInputGetState(0, state) = ERROR_SUCCESS then
+ begin
+ key := MappableInputs.XInputStateToKey(state);
+ if key <> EnumXInputButtons.xiUnbound then
+ begin
+ XInputKey := key;
+ ModalResult := mrOk;
+ end;
+ end;
+end;
+
+procedure TFormBindButtons.BitBtn2Click(Sender: TObject);
+begin
+ XInputKey := xiUnbound;
+ ModalResult := mrOk;
+end;
+
+procedure TFormBindButtons.BitBtn1Click(Sender: TObject);
+begin
+ ModalResult := mrCancel;
+end;
+
+procedure TFormBindButtons.FormShow(Sender: TObject);
+begin
+ XInputKey := xiUnbound;
+end;
+
+end.
diff --git a/src/inputs/formcontroller.lfm b/src/inputs/formcontroller.lfm
new file mode 100644
index 00000000..c9d45ad5
--- /dev/null
+++ b/src/inputs/formcontroller.lfm
@@ -0,0 +1,671 @@
+object FormControllers: TFormControllers
+ Left = 502
+ Height = 1123
+ Top = 247
+ Width = 1873
+ Caption = 'XInput to PS4 Controller Config'
+ ClientHeight = 1123
+ ClientWidth = 1873
+ DesignTimePPI = 192
+ OnClose = FormClose
+ OnCreate = FormCreate
+ OnShow = FormShow
+ LCLVersion = '2.3.0.0'
+ object GroupBox1: TGroupBox
+ Left = 24
+ Height = 328
+ Top = 312
+ Width = 640
+ Caption = 'Left Joystick'
+ ClientHeight = 291
+ ClientWidth = 636
+ TabOrder = 0
+ object Shape1: TShape
+ Left = 240
+ Height = 130
+ Top = 72
+ Width = 130
+ Shape = stCircle
+ end
+ object LJoyUp: TBitBtn
+ Tag = 1
+ Left = 200
+ Height = 43
+ Top = 24
+ Width = 212
+ Caption = 'UP'
+ OnClick = ChangeKeyBiding
+ TabOrder = 0
+ end
+ object LJoyRight: TBitBtn
+ Tag = 4
+ Left = 400
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'RIGHT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 1
+ end
+ object LJoyDown: TBitBtn
+ Tag = 2
+ Left = 200
+ Height = 43
+ Top = 240
+ Width = 212
+ Caption = 'DOWN'
+ OnClick = ChangeKeyBiding
+ TabOrder = 2
+ end
+ object LJoyLeft: TBitBtn
+ Tag = 3
+ Left = 8
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'LEFT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 3
+ end
+ object Label1: TLabel
+ Left = 480
+ Height = 32
+ Top = 80
+ Width = 56
+ Alignment = taCenter
+ Caption = 'Right'
+ end
+ object Label2: TLabel
+ Left = 288
+ Height = 32
+ Top = -16
+ Width = 30
+ Alignment = taCenter
+ Caption = 'Up'
+ end
+ object Label3: TLabel
+ Left = 88
+ Height = 32
+ Top = 80
+ Width = 40
+ Alignment = taCenter
+ Caption = 'Left'
+ end
+ object Label4: TLabel
+ Left = 272
+ Height = 32
+ Top = 200
+ Width = 62
+ Alignment = taCenter
+ Caption = 'Down'
+ end
+ end
+ object GroupBox6: TGroupBox
+ Left = 464
+ Height = 258
+ Top = 8
+ Width = 578
+ Caption = 'Shoulder buttons / Triggers'
+ ClientHeight = 221
+ ClientWidth = 574
+ TabOrder = 1
+ object R3: TBitBtn
+ Tag = 25
+ Left = 344
+ Height = 60
+ Top = 16
+ Width = 184
+ Caption = 'L3'
+ OnClick = ChangeKeyBiding
+ TabOrder = 0
+ end
+ object L3: TBitBtn
+ Tag = 22
+ Left = 56
+ Height = 60
+ Top = 16
+ Width = 184
+ Caption = 'L3'
+ OnClick = ChangeKeyBiding
+ TabOrder = 1
+ end
+ object R2: TBitBtn
+ Tag = 24
+ Left = 344
+ Height = 60
+ Top = 80
+ Width = 184
+ Caption = 'L2'
+ OnClick = ChangeKeyBiding
+ TabOrder = 2
+ end
+ object L2: TBitBtn
+ Tag = 21
+ Left = 56
+ Height = 60
+ Top = 80
+ Width = 184
+ Caption = 'L2'
+ OnClick = ChangeKeyBiding
+ TabOrder = 3
+ end
+ object L1: TBitBtn
+ Tag = 20
+ Left = 56
+ Height = 60
+ Top = 144
+ Width = 184
+ Caption = 'L1'
+ OnClick = ChangeKeyBiding
+ TabOrder = 4
+ end
+ object R1: TBitBtn
+ Tag = 23
+ Left = 344
+ Height = 60
+ Top = 144
+ Width = 184
+ Caption = 'L1'
+ OnClick = ChangeKeyBiding
+ TabOrder = 5
+ end
+ object Label17: TLabel
+ Left = 16
+ Height = 32
+ Top = 24
+ Width = 24
+ Alignment = taCenter
+ Caption = 'L3'
+ end
+ object Label18: TLabel
+ Left = 16
+ Height = 32
+ Top = 88
+ Width = 24
+ Alignment = taCenter
+ Caption = 'L2'
+ end
+ object Label19: TLabel
+ Left = 16
+ Height = 32
+ Top = 160
+ Width = 24
+ Alignment = taCenter
+ Caption = 'L1'
+ end
+ object Label20: TLabel
+ Left = 280
+ Height = 32
+ Top = 24
+ Width = 27
+ Alignment = taCenter
+ Caption = 'R3'
+ end
+ object Label21: TLabel
+ Left = 280
+ Height = 32
+ Top = 88
+ Width = 27
+ Alignment = taCenter
+ Caption = 'R2'
+ end
+ object Label22: TLabel
+ Left = 280
+ Height = 32
+ Top = 160
+ Width = 27
+ Alignment = taCenter
+ Caption = 'R1'
+ end
+ end
+ object BitBtn17: TBitBtn
+ Tag = 19
+ Left = 1480
+ Height = 60
+ Top = 112
+ Width = 182
+ Caption = 'Start'
+ OnClick = ChangeKeyBiding
+ TabOrder = 2
+ end
+ object BitBtn18: TBitBtn
+ Tag = 17
+ Left = 1064
+ Height = 60
+ Top = 112
+ Width = 184
+ Caption = 'Share'
+ OnClick = ChangeKeyBiding
+ TabOrder = 3
+ end
+ object BitBtn19: TBitBtn
+ Tag = 18
+ Left = 1272
+ Height = 60
+ Top = 112
+ Width = 184
+ Caption = 'Touch Screen'
+ OnClick = ChangeKeyBiding
+ TabOrder = 4
+ end
+ object GroupBox3: TGroupBox
+ Left = 24
+ Height = 328
+ Top = 664
+ Width = 640
+ Caption = 'DPAD'
+ ClientHeight = 291
+ ClientWidth = 636
+ TabOrder = 5
+ object DPadUp: TBitBtn
+ Tag = 9
+ Left = 200
+ Height = 43
+ Top = 24
+ Width = 212
+ Caption = 'UP'
+ OnClick = ChangeKeyBiding
+ TabOrder = 0
+ end
+ object DPadRight: TBitBtn
+ Tag = 12
+ Left = 400
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'RIGHT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 1
+ end
+ object DPadDown: TBitBtn
+ Tag = 10
+ Left = 200
+ Height = 43
+ Top = 240
+ Width = 212
+ Caption = 'DOWN'
+ OnClick = ChangeKeyBiding
+ TabOrder = 2
+ end
+ object DPadLeft: TBitBtn
+ Tag = 11
+ Left = 8
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'LEFT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 3
+ end
+ object Label5: TLabel
+ Left = 480
+ Height = 32
+ Top = 80
+ Width = 56
+ Alignment = taCenter
+ Caption = 'Right'
+ end
+ object Label6: TLabel
+ Left = 288
+ Height = 32
+ Top = -16
+ Width = 30
+ Alignment = taCenter
+ Caption = 'Up'
+ end
+ object Label7: TLabel
+ Left = 88
+ Height = 32
+ Top = 80
+ Width = 40
+ Alignment = taCenter
+ Caption = 'Left'
+ end
+ object Label8: TLabel
+ Left = 272
+ Height = 32
+ Top = 200
+ Width = 62
+ Alignment = taCenter
+ Caption = 'Down'
+ end
+ object Shape2: TShape
+ Left = 283
+ Height = 34
+ Top = 80
+ Width = 39
+ Shape = stTriangle
+ end
+ object Shape5: TShape
+ Left = 283
+ Height = 34
+ Top = 160
+ Width = 39
+ Shape = stTriangleDown
+ end
+ object Shape6: TShape
+ Left = 331
+ Height = 34
+ Top = 120
+ Width = 39
+ Shape = stTriangleRight
+ end
+ object Shape7: TShape
+ Left = 235
+ Height = 34
+ Top = 120
+ Width = 39
+ Shape = stTriangleLeft
+ end
+ end
+ object GroupBox4: TGroupBox
+ Left = 712
+ Height = 328
+ Top = 664
+ Width = 640
+ Caption = 'Right joystick'
+ ClientHeight = 291
+ ClientWidth = 636
+ TabOrder = 6
+ object Shape3: TShape
+ Left = 240
+ Height = 130
+ Top = 72
+ Width = 130
+ Shape = stCircle
+ end
+ object RJoyUp: TBitBtn
+ Tag = 5
+ Left = 200
+ Height = 43
+ Top = 24
+ Width = 212
+ Caption = 'UP'
+ OnClick = ChangeKeyBiding
+ TabOrder = 0
+ end
+ object RJoyRight: TBitBtn
+ Tag = 8
+ Left = 400
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'RIGHT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 1
+ end
+ object RJoyDown: TBitBtn
+ Tag = 6
+ Left = 200
+ Height = 43
+ Top = 240
+ Width = 212
+ Caption = 'DOWN'
+ OnClick = ChangeKeyBiding
+ TabOrder = 2
+ end
+ object RJoyLeft: TBitBtn
+ Tag = 7
+ Left = 8
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'LEFT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 3
+ end
+ object Label9: TLabel
+ Left = 488
+ Height = 32
+ Top = 80
+ Width = 56
+ Alignment = taCenter
+ Caption = 'Right'
+ end
+ object Label10: TLabel
+ Left = 288
+ Height = 32
+ Top = -16
+ Width = 30
+ Alignment = taCenter
+ Caption = 'Up'
+ end
+ object Label11: TLabel
+ Left = 88
+ Height = 32
+ Top = 80
+ Width = 40
+ Alignment = taCenter
+ Caption = 'Left'
+ end
+ object Label12: TLabel
+ Left = 272
+ Height = 32
+ Top = 200
+ Width = 62
+ Alignment = taCenter
+ Caption = 'Down'
+ end
+ end
+ object GroupBox5: TGroupBox
+ Left = 712
+ Height = 328
+ Top = 312
+ Width = 640
+ Caption = 'Buttons'
+ ClientHeight = 291
+ ClientWidth = 636
+ TabOrder = 7
+ object Triangle: TBitBtn
+ Tag = 16
+ Left = 200
+ Height = 43
+ Top = 24
+ Width = 212
+ Caption = 'UP'
+ OnClick = ChangeKeyBiding
+ TabOrder = 0
+ end
+ object Circle: TBitBtn
+ Tag = 14
+ Left = 400
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'RIGHT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 1
+ end
+ object Cross: TBitBtn
+ Tag = 13
+ Left = 200
+ Height = 43
+ Top = 240
+ Width = 212
+ Caption = 'DOWN'
+ OnClick = ChangeKeyBiding
+ TabOrder = 2
+ end
+ object Square: TBitBtn
+ Tag = 15
+ Left = 8
+ Height = 43
+ Top = 120
+ Width = 212
+ Caption = 'LEFT'
+ OnClick = ChangeKeyBiding
+ TabOrder = 3
+ end
+ object Label13: TLabel
+ Left = 480
+ Height = 32
+ Top = 80
+ Width = 59
+ Alignment = taCenter
+ Caption = 'Circle'
+ end
+ object Label14: TLabel
+ Left = 264
+ Height = 32
+ Top = -16
+ Width = 84
+ Alignment = taCenter
+ Caption = 'Triangle'
+ end
+ object Label15: TLabel
+ Left = 72
+ Height = 32
+ Top = 80
+ Width = 74
+ Alignment = taCenter
+ Caption = 'Square'
+ end
+ object Label16: TLabel
+ Left = 272
+ Height = 32
+ Top = 200
+ Width = 57
+ Alignment = taCenter
+ Caption = 'Cross'
+ end
+ end
+ object Label23: TLabel
+ Left = 1128
+ Height = 32
+ Top = 72
+ Width = 60
+ Alignment = taCenter
+ Caption = 'Share'
+ end
+ object Label24: TLabel
+ Left = 1296
+ Height = 32
+ Top = 72
+ Width = 143
+ Alignment = taCenter
+ Caption = 'Touch Screen'
+ end
+ object Label25: TLabel
+ Left = 1528
+ Height = 32
+ Top = 72
+ Width = 84
+ Alignment = taCenter
+ Caption = 'Options'
+ end
+ object Save: TBitBtn
+ Left = 1352
+ Height = 60
+ Top = 1032
+ Width = 226
+ Caption = 'Save'
+ OnClick = SaveClick
+ TabOrder = 8
+ end
+ object Cancel: TBitBtn
+ Left = 1618
+ Height = 60
+ Top = 1032
+ Width = 226
+ Caption = 'Cancel'
+ OnClick = CancelClick
+ TabOrder = 9
+ end
+ object SavePreset: TBitBtn
+ Left = 56
+ Height = 60
+ Top = 104
+ Width = 226
+ Caption = 'Save as preset'
+ OnClick = SavePresetClick
+ TabOrder = 10
+ end
+ object LoadPreset: TBitBtn
+ Left = 56
+ Height = 60
+ Top = 184
+ Width = 226
+ Caption = 'Load preset'
+ OnClick = LoadPresetClick
+ TabOrder = 11
+ end
+ object CheckEnabled: TCheckBox
+ Left = 64
+ Height = 36
+ Top = 32
+ Width = 193
+ Caption = 'XInput Enabled'
+ Checked = True
+ OnChange = SaveConfig
+ State = cbChecked
+ TabOrder = 12
+ end
+ object GroupBox2: TGroupBox
+ Left = 1377
+ Height = 282
+ Top = 368
+ Width = 434
+ Caption = 'Dead zones'
+ ClientHeight = 245
+ ClientWidth = 430
+ TabOrder = 13
+ object dzLeft: TTrackBar
+ Left = 112
+ Height = 50
+ Top = 24
+ Width = 280
+ OnChange = SaveConfig
+ Position = 0
+ TabOrder = 0
+ end
+ object dzRight: TTrackBar
+ Left = 112
+ Height = 50
+ Top = 96
+ Width = 280
+ OnChange = SaveConfig
+ Position = 0
+ TabOrder = 1
+ end
+ object Label26: TLabel
+ Left = 16
+ Height = 32
+ Top = 24
+ Width = 40
+ Caption = 'Left'
+ end
+ object Label27: TLabel
+ Left = 16
+ Height = 32
+ Top = 96
+ Width = 56
+ Caption = 'Right'
+ end
+ object Label28: TLabel
+ Left = 16
+ Height = 32
+ Top = 168
+ Width = 84
+ Caption = 'Triggers'
+ end
+ object dzTriggers: TTrackBar
+ Left = 112
+ Height = 50
+ Top = 168
+ Width = 280
+ OnChange = SaveConfig
+ Position = 0
+ TabOrder = 2
+ end
+ end
+ object OpenDialog1: TOpenDialog
+ Filter = 'Ini files|*.ini'
+ Left = 328
+ Top = 66
+ end
+ object SaveDialog1: TSaveDialog
+ Filter = 'Ini file|*.ini'
+ Left = 328
+ Top = 184
+ end
+end
diff --git a/src/inputs/formcontroller.pas b/src/inputs/formcontroller.pas
new file mode 100644
index 00000000..dc5ceaec
--- /dev/null
+++ b/src/inputs/formcontroller.pas
@@ -0,0 +1,246 @@
+unit formController;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ExtCtrls,
+ Buttons, Interfaces, LResources, ComCtrls;
+
+type
+
+ { TFormControllers }
+
+ TFormControllers = class(TForm)
+ CheckEnabled: TCheckBox;
+ GroupBox2: TGroupBox;
+ Label26: TLabel;
+ Label27: TLabel;
+ Label28: TLabel;
+ LJoyUp: TBitBtn;
+ OpenDialog1: TOpenDialog;
+ RJoyRight: TBitBtn;
+ RJoyDown: TBitBtn;
+ RJoyLeft: TBitBtn;
+ dzLeft: TTrackBar;
+ dzRight: TTrackBar;
+ dzTriggers: TTrackBar;
+ SaveDialog1: TSaveDialog;
+ Triangle: TBitBtn;
+ Circle: TBitBtn;
+ Cross: TBitBtn;
+ Square: TBitBtn;
+ BitBtn17: TBitBtn;
+ BitBtn18: TBitBtn;
+ BitBtn19: TBitBtn;
+ LJoyLeft: TBitBtn;
+ L1: TBitBtn;
+ L2: TBitBtn;
+ L3: TBitBtn;
+ R1: TBitBtn;
+ R2: TBitBtn;
+ R3: TBitBtn;
+ Save: TBitBtn;
+ Cancel: TBitBtn;
+ SavePreset: TBitBtn;
+ LoadPreset: TBitBtn;
+ LJoyRight: TBitBtn;
+ LJoyDown: TBitBtn;
+ DPadUp: TBitBtn;
+ DPadRight: TBitBtn;
+ DPadDown: TBitBtn;
+ DPadLeft: TBitBtn;
+ RJoyUp: TBitBtn;
+ GroupBox1: TGroupBox;
+ GroupBox3: TGroupBox;
+ GroupBox4: TGroupBox;
+ GroupBox5: TGroupBox;
+ GroupBox6: TGroupBox;
+ Label1: TLabel;
+ Label10: TLabel;
+ Label11: TLabel;
+ Label12: TLabel;
+ Label13: TLabel;
+ Label14: TLabel;
+ Label15: TLabel;
+ Label16: TLabel;
+ Label17: TLabel;
+ Label18: TLabel;
+ Label19: TLabel;
+ Label2: TLabel;
+ Label20: TLabel;
+ Label21: TLabel;
+ Label22: TLabel;
+ Label23: TLabel;
+ Label24: TLabel;
+ Label25: TLabel;
+ Label3: TLabel;
+ Label4: TLabel;
+ Label5: TLabel;
+ Label6: TLabel;
+ Label7: TLabel;
+ Label8: TLabel;
+ Label9: TLabel;
+ Shape1: TShape;
+ Shape2: TShape;
+ Shape3: TShape;
+ Shape5: TShape;
+ Shape6: TShape;
+ Shape7: TShape;
+ procedure CancelClick(Sender: TObject);
+ procedure ChangeKeyBiding(Sender: TObject);
+ procedure LoadPresetClick(Sender: TObject);
+ procedure SaveConfig(Sender: TObject);
+ procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
+ procedure FormCreate(Sender: TObject);
+ procedure FormShow(Sender: TObject);
+ procedure SaveClick(Sender: TObject);
+ procedure SavePresetClick(Sender: TObject);
+ private
+ ShouldSaveConfig: boolean;
+ function FindControlByTag(control: TWinControl; pTag: integer): TWinControl;
+ public
+
+ end;
+
+var
+ FormControllers: TFormControllers;
+
+implementation
+
+{$R *.lfm}
+
+uses formBindButton, uMappableInputs;
+
+{ TFormControllers }
+
+procedure TFormControllers.ChangeKeyBiding(Sender: TObject);
+var
+ keyToChange: integer;
+begin
+ if Sender is TBitBtn then
+ begin
+ keyToChange := TBitBtn(Sender).Tag;
+ FormBindButtons := TFormBindButtons.Create(Self);
+ Application.CreateForm(TFormBindButtons, FormBindButtons);
+ FormBindButtons.ShowModal;
+
+ if FormBindButtons.ModalResult = mrOk then
+ begin
+ MappableInputs.SetXInputMapping(EnumPS4Buttons(keyToChange), FormBindButtons.XInputKey);
+ TBitBtn(Sender).Caption := MappableInputs.XInputButtonsNames[Ord(FormBindButtons.XInputKey)];
+ end;
+
+ FormBindButtons.Release;
+ FormBindButtons.Free;
+ end;
+end;
+
+procedure TFormControllers.LoadPresetClick(Sender: TObject);
+begin
+ //
+ OpenDialog1.InitialDir:='config';
+ if OpenDialog1.Execute then
+ begin
+ MappableInputs.LoadFromFile(OpenDialog1.FileName);
+ ShowMessage('Config loaded from ' + OpenDialog1.FileName);
+ end;
+end;
+
+procedure TFormControllers.SaveConfig(Sender: TObject);
+begin
+ if ShouldSaveConfig then
+ begin
+ MappableInputs.XInputEnabled := CheckEnabled.Checked;
+ MappableInputs.XInputDeadzoneLeft := dzLeft.Position;
+ MappableInputs.XInputDeadzoneRight := dzRight.Position;
+ MappableInputs.XInputDeadzoneTrigger := dzTriggers.Position;
+ end;
+end;
+
+procedure TFormControllers.FormClose(Sender: TObject; var CloseAction: TCloseAction);
+begin
+ MappableInputs.LoadFromFile(XINPUT_CONFIG_FILE);
+ CloseAction := TCloseAction.caHide;
+end;
+
+procedure TFormControllers.CancelClick(Sender: TObject);
+begin
+ Self.Close;
+end;
+
+function TFormControllers.FindControlByTag(control: TWinControl; pTag: integer): TWinControl;
+var
+ i: integer;
+begin
+ Result := nil;
+ for i := 0 to control.ControlCount - 1 do
+ begin
+ if control.Controls[i] is TWinControl then
+ begin
+ if control.Controls[i].Tag = pTag then
+ begin
+ Result := TWinControl(control.Controls[i]);
+ exit;
+ end;
+ // recursively check children
+ Result := FindControlByTag(TWinControl(control.Controls[i]), pTag);
+ if Result <> nil then
+ exit;
+ end;
+ end;
+end;
+
+procedure TFormControllers.FormCreate(Sender: TObject);
+begin
+end;
+
+procedure TFormControllers.FormShow(Sender: TObject);
+var
+ i: integer;
+ btn: TWinControl;
+begin
+ // Load mapped buttons into UI
+ ShouldSaveConfig := False;
+ MappableInputs.LoadFromFile(XINPUT_CONFIG_FILE);
+ CheckEnabled.Checked := MappableInputs.XInputEnabled;
+
+ dzTriggers.Max := 255;
+
+ dzLeft.Max := smallint.MaxValue;
+ dzRight.Max := smallint.MaxValue;
+
+ dzLeft.Position := MappableInputs.XInputDeadzoneLeft;
+ dzRight.Position := MappableInputs.XInputDeadzoneRight;
+ dzTriggers.Position := MappableInputs.XInputDeadzoneTrigger;
+
+ for i := 1 to NUM_PS4_BUTTONS do
+ begin
+ btn := FindControlByTag(Self, i);
+ if btn <> nil then
+ begin
+ btn.Caption := MappableInputs.XInputButtonsNames[Ord(MappableInputs.PS4toXInput[i])];
+ end;
+ end;
+ ShouldSaveConfig := True;
+end;
+
+procedure TFormControllers.SaveClick(Sender: TObject);
+begin
+ MappableInputs.SaveToFile(XINPUT_CONFIG_FILE);
+ Self.Close;
+end;
+
+procedure TFormControllers.SavePresetClick(Sender: TObject);
+begin
+ //
+ SaveDialog1.InitialDir:='config';
+ if SaveDialog1.Execute then
+ begin
+ MappableInputs.SaveToFile(SaveDialog1.FileName);
+ ShowMessage('Config saved to ' + SaveDialog1.FileName);
+ end;
+end;
+
+end.
diff --git a/src/inputs/umappableinputs.pas b/src/inputs/umappableinputs.pas
new file mode 100644
index 00000000..3c7f6f9d
--- /dev/null
+++ b/src/inputs/umappableinputs.pas
@@ -0,0 +1,382 @@
+unit uMappableInputs;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, XInput, IniFiles, Math;
+
+const
+ NUM_PS4_BUTTONS = 25;
+ NUM_XINPUT_BUTTONS = 25;
+ XINPUT_CONFIG_FILE = 'config/xinput.ini';
+
+type
+ // Mappable inputs of the PS4 controller
+ EnumPS4Buttons = (
+ miUnbound = 0,
+ miLJoyUp = 1,
+ miLJoyDown = 2,
+ miLJoyLeft = 3,
+ miLJoyRight = 4,
+
+ miRJoyUp = 5,
+ miRJoyDown = 6,
+ miRJoyLeft = 7,
+ miRJoyRight = 8,
+
+ miDPadUp = 9,
+ miDPadDown = 10,
+ miDPadLeft = 11,
+ miDPadRight = 12,
+
+ miCross = 13,
+ miCircle = 14,
+ miSquare = 15,
+ miTriangle = 16,
+
+ miShare = 17,
+ miTouchPad = 18,
+ miOptions = 19,
+
+ miL1 = 20,
+ miL2 = 21,
+ miL3 = 22,
+
+ miR1 = 23,
+ miR2 = 24,
+ miR3 = 25
+ );
+
+ // XInput buttons enum
+ EnumXInputButtons = (
+ xiUnbound = 0,
+ xiLJoyUp = 1,
+ xiLJoyDown = 2,
+ xiLJoyLeft = 3,
+ xiLJoyRight = 4,
+
+ xiRJoyUp = 5,
+ xiRJoyDown = 6,
+ xiRJoyLeft = 7,
+ xiRJoyRight = 8,
+
+ xiDPadUp = 9,
+ xiDPadDown = 10,
+ xiDPadLeft = 11,
+ xiDPadRight = 12,
+
+ xiA = 13,
+ xiB = 14,
+ xiX = 15,
+ xiY = 16,
+
+ xiSelect = 17,
+ xiUnused01 = 18,
+ xiStart = 19,
+
+ xiL1 = 20,
+ xiL2 = 21,
+ xiL3 = 22,
+
+ xiR1 = 23,
+ xiR2 = 24,
+ xiR3 = 25
+ );
+
+type
+ TMappableInputs = class(TObject)
+ public
+ XInputEnabled: boolean;
+
+ PS4toXInput: array[0..NUM_PS4_BUTTONS] of EnumXInputButtons;
+ XInputToPS4: array[0..NUM_XINPUT_BUTTONS] of EnumPS4Buttons;
+
+ XInputButtonsNames: array[0..NUM_XINPUT_BUTTONS] of string;
+ XInputDeadzoneLeft, XInputDeadzoneRight: integer;
+ XInputDeadzoneTrigger : Byte;
+
+ function GetAnalog(input : EnumPS4Buttons; s : TXInputState) : Single;
+ function PS4IsPressed(input: EnumPS4Buttons; s : TXInputState): boolean;
+
+ function XInputStateToKey(state: TXInputState): EnumXInputButtons;
+ function XInputIsTriggered(input: EnumXInputButtons; state: TXInputState): boolean;
+ function XInputIsAnalog(input: EnumXInputButtons): boolean;
+ procedure SetXInputMapping(mappableInput: EnumPS4Buttons; xinput: EnumXInputButtons);
+
+ // Save / load
+ function LoadFromFile(filePath: string): boolean;
+ function SaveToFile(filePath: string): boolean;
+ end;
+
+var
+ MappableInputs: TMappableInputs;
+
+
+implementation
+
+uses TypInfo;
+
+procedure TMappableInputs.SetXInputMapping(mappableInput: EnumPS4Buttons; xinput: EnumXInputButtons);
+begin
+ XInputToPS4[Ord(xinput)] := mappableInput;
+ PS4toXInput[Ord(mappableInput)] := xinput;
+end;
+
+function TMappableInputs.XInputStateToKey(state: TXInputState): EnumXInputButtons;
+var
+ i: integer;
+begin
+ Result := xiUnbound;
+ for i := 1 to NUM_XINPUT_BUTTONS do
+ begin
+ if XInputIsTriggered(EnumXInputButtons(i), state) then
+ begin
+ Result := EnumXInputButtons(i);
+ exit;
+ end;
+ end;
+end;
+
+function TMappableInputs.XInputIsAnalog(input: EnumXInputButtons): boolean;
+begin
+ case (input) of
+ xiLJoyUp, xiLJoyLeft, xiLJoyRight, xiLJoyDown: Result := True;
+ xiRJoyUp, xiRJoyLeft, xiRJoyRight, xiRJoyDown: Result := True;
+ xiL2, xiR2: Result := True;
+ else Result := false;
+ end;
+end;
+
+function TMappableInputs.GetAnalog(input : EnumPS4Buttons; s : TXInputState) : Single;
+var
+ xinputButton : EnumXInputButtons;
+ outOfDeadzoneL, outOfDeadzoneR, outOfDeadzoneT : single;
+begin
+ xInputButton := PS4toXInput[Ord(input)];
+
+ if XInputIsAnalog(xInputButton) then
+ begin
+ outOfDeadzoneL := IfThen(Trunc(sqrt(s.Gamepad.sThumbLX*s.Gamepad.sThumbLX+s.Gamepad.sThumbLY*s.Gamepad.sThumbLY)) > XInputDeadzoneLeft, 1, 0);
+ outOfDeadzoneR := IfThen(Trunc(sqrt(s.Gamepad.sThumbRX*s.Gamepad.sThumbRX+s.Gamepad.sThumbRY*s.Gamepad.sThumbRY)) > XInputDeadzoneRight, 1, 0);
+ outOfDeadzoneT := IfThen(s.Gamepad.bLeftTrigger > XInputDeadzoneTrigger, 1, 0);
+
+ case (xInputButton) of
+ xiLJoyUp: Result := Max(s.Gamepad.sThumbLY, 0) * outOfDeadzoneL / 32767.0;
+ xiLJoyDown: Result := Min(s.Gamepad.sThumbLY, 0) * outOfDeadzoneL / 32767.0;
+ xiLJoyRight: Result := Max(s.Gamepad.sThumbLX, 0) * outOfDeadzoneL / 32767.0;
+ xiLJoyLeft: Result := Min(s.Gamepad.sThumbLX, 0) * outOfDeadzoneL / 32767.0;
+
+ xiRJoyUp: Result := s.Gamepad.sThumbRY * outOfDeadzoneR / 32767.0;
+ xiRJoyDown: Result := s.Gamepad.sThumbRY * outOfDeadzoneR / 32767.0;
+ xiRJoyRight: Result := s.Gamepad.sThumbRX * outOfDeadzoneR / 32767.0;
+ xiRJoyLeft: Result := s.Gamepad.sThumbRX * outOfDeadzoneR / 32767.0;
+
+ xiL2: Result := IfThen(s.Gamepad.bLeftTrigger > XInputDeadzoneTrigger, s.Gamepad.bLeftTrigger / 255.0, 0);
+ xiR2: Result := IfThen(s.Gamepad.bRightTrigger > XInputDeadzoneTrigger, s.Gamepad.bLeftTrigger / 255.0, 0);
+ else Result := 0;
+ end;
+
+ end else begin
+ // is button
+ Result := IfThen(XInputIsTriggered(xinputButton, s), 1.0, 0.0);
+ end;
+
+ // clamp between 0 and 1
+ Result := Max(Min(Abs(Result), 1.0), 0.00);
+end;
+
+function TMappableInputs.PS4IsPressed(input: EnumPS4Buttons; s: TXInputState): boolean;
+begin
+ Result := XInputIsTriggered(PS4toXInput[Ord(input)], s);
+end;
+
+function TMappableInputs.LoadFromFile(filePath: string): boolean;
+var
+ iniFile: TIniFile;
+ i: integer;
+ xInputButton : Integer;
+begin
+ Result := False;
+
+ if not FileExists(filePath) then exit;
+
+ iniFile := TIniFile.Create(filePath);
+ Self.XInputEnabled := iniFile.ReadBool('XInput', 'XInputEnabled', True);
+ Self.XInputDeadzoneLeft := iniFile.ReadInteger('XInput', 'XInputDeadzoneLeft', XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
+ Self.XInputDeadzoneRight := iniFile.ReadInteger('XInput', 'XInputDeadzoneRight', XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
+ Self.XInputDeadzoneTrigger := iniFile.ReadInteger('XInput', 'XInputDeadzoneTrigger', XINPUT_GAMEPAD_TRIGGER_THRESHOLD);
+ for i := 1 to NUM_PS4_BUTTONS do
+ begin
+ xInputButton := iniFile.ReadInteger('XInput', GetEnumName(TypeInfo(EnumPS4Buttons), i), 0);
+ SetXInputMapping(EnumPS4Buttons(i), EnumXInputButtons(xInputButton));
+ end;
+ iniFile.Free;
+
+ Result := True;
+end;
+
+function TMappableInputs.SaveToFile(filePath: string): boolean;
+var
+ iniFile: TIniFile;
+ i: integer;
+ xInputButton : Integer;
+begin
+ Result := False;
+
+ iniFile := TIniFile.Create(filePath);
+ iniFile.WriteBool('XInput', 'XInputEnabled', Self.XInputEnabled);
+ iniFile.WriteInteger('XInput', 'XInputDeadzoneLeft', Self.XInputDeadzoneLeft);
+ iniFile.WriteInteger('XInput', 'XInputDeadzoneRight', Self.XInputDeadzoneRight);
+ iniFile.WriteInteger('XInput', 'XInputDeadzoneTrigger', Self.XInputDeadzoneTrigger);
+ for i := 1 to NUM_PS4_BUTTONS do
+ begin
+ xInputButton := Ord(PS4toXInput[i]);
+ iniFile.WriteInteger('XInput', GetEnumName(TypeInfo(EnumPS4Buttons), i), xInputButton);
+ end;
+ iniFile.Free;
+
+ Result := True;
+end;
+
+function TMappableInputs.XInputIsTriggered(input: EnumXInputButtons; state: TXInputState): boolean;
+begin
+ case (input) of
+ xiLJoyUp: Result := state.Gamepad.sThumbLY > self.XInputDeadzoneLeft;
+ xiLJoyDown: Result := state.Gamepad.sThumbLY < -self.XInputDeadzoneLeft;
+ xiLJoyLeft: Result := state.Gamepad.sThumbLX < -self.XInputDeadzoneLeft;
+ xiLJoyRight: Result := state.Gamepad.sThumbLX > self.XInputDeadzoneLeft;
+
+ xiRJoyUp: Result := state.Gamepad.sThumbRY > self.XInputDeadzoneRight;
+ xiRJoyDown: Result := state.Gamepad.sThumbRY < -self.XInputDeadzoneRight;
+ xiRJoyLeft: Result := state.Gamepad.sThumbRX < -self.XInputDeadzoneRight;
+ xiRJoyRight: Result := state.Gamepad.sThumbRX > self.XInputDeadzoneRight;
+
+ xiA: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_A) <> 0;
+ xiB: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_B) <> 0;
+ xiX: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_X) <> 0;
+ xiY: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_Y) <> 0;
+
+ xiDPadUp: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_UP) <> 0;
+ xiDPadDown: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_DOWN) <> 0;
+ xiDPadLeft: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_LEFT) <> 0;
+ xiDPadRight: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_RIGHT) <> 0;
+
+ xiSelect: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_BACK) <> 0;
+ xiStart: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_START) <> 0;
+
+ xiL1: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_LEFT_SHOULDER) <> 0;
+ xiL2: Result := state.Gamepad.bLeftTrigger > self.XInputDeadzoneTrigger;
+ xiL3: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_LEFT_THUMB) <> 0;
+ xiR1: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_RIGHT_SHOULDER) <> 0;
+ xiR2: Result := state.Gamepad.bRightTrigger > self.XInputDeadzoneTrigger;
+ xiR3: Result := (state.Gamepad.wButtons and XINPUT_GAMEPAD_RIGHT_THUMB) <> 0;
+ else Result := false;
+ end;
+end;
+
+initialization
+
+ MappableInputs := TMappableInputs.Create;
+
+ MappableInputs.XInputButtonsNames[Ord(xiUnbound)] := 'Unbound';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyUp)] := 'LJOY_UP';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyDown)] := 'LJOY_DOWN';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyLeft)] := 'LJOY_LEFT';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyRight)] := 'LJOY_RIGHT';
+
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyUp)] := 'RJOY_UP';
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyDown)] := 'RJOY_DOWN';
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyLeft)] := 'RJOY_LEFT';
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyRight)] := 'RJOY_RIGHT';
+
+ MappableInputs.XInputButtonsNames[Ord(xiDPadUp)] := 'DPAD_UP';
+ MappableInputs.XInputButtonsNames[Ord(xiDPadDown)] := 'DPAD_DOWN';
+ MappableInputs.XInputButtonsNames[Ord(xiDPadLeft)] := 'DPAD_LEFT';
+ MappableInputs.XInputButtonsNames[Ord(xiDPadRight)] := 'DPAD_RIGHT';
+
+ MappableInputs.XInputButtonsNames[Ord(xiA)] := 'A';
+ MappableInputs.XInputButtonsNames[Ord(xiB)] := 'B';
+ MappableInputs.XInputButtonsNames[Ord(xiX)] := 'X';
+ MappableInputs.XInputButtonsNames[Ord(xiY)] := 'Y';
+
+ MappableInputs.XInputButtonsNames[Ord(xiSelect)] := 'SELECT';
+ MappableInputs.XInputButtonsNames[Ord(xiUnused01)] := 'UNUSED01';
+ MappableInputs.XInputButtonsNames[Ord(xiStart)] := 'START';
+
+ MappableInputs.XInputButtonsNames[Ord(xiL1)] := 'L1';
+ MappableInputs.XInputButtonsNames[Ord(xiL2)] := 'L2';
+ MappableInputs.XInputButtonsNames[Ord(xiL3)] := 'L3';
+
+ MappableInputs.XInputButtonsNames[Ord(xiR1)] := 'R1';
+ MappableInputs.XInputButtonsNames[Ord(xiR2)] := 'R2';
+ MappableInputs.XInputButtonsNames[Ord(xiR3)] := 'R3';
+
+ MappableInputs.XInputButtonsNames[Ord(xiUnbound)] := 'Unbound';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyUp)] := 'LJOY_UP';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyDown)] := 'LJOY_DOWN';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyLeft)] := 'LJOY_LEFT';
+ MappableInputs.XInputButtonsNames[Ord(xiLJoyRight)] := 'LJOY_RIGHT';
+
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyUp)] := 'RJOY_UP';
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyDown)] := 'RJOY_DOWN';
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyLeft)] := 'RJOY_LEFT';
+ MappableInputs.XInputButtonsNames[Ord(xiRJoyRight)] := 'RJOY_RIGHT';
+
+ MappableInputs.XInputButtonsNames[Ord(xiDPadUp)] := 'DPAD_UP';
+ MappableInputs.XInputButtonsNames[Ord(xiDPadDown)] := 'DPAD_DOWN';
+ MappableInputs.XInputButtonsNames[Ord(xiDPadLeft)] := 'DPAD_LEFT';
+ MappableInputs.XInputButtonsNames[Ord(xiDPadRight)] := 'DPAD_RIGHT';
+
+ MappableInputs.XInputButtonsNames[Ord(xiA)] := 'A';
+ MappableInputs.XInputButtonsNames[Ord(xiB)] := 'B';
+ MappableInputs.XInputButtonsNames[Ord(xiX)] := 'X';
+ MappableInputs.XInputButtonsNames[Ord(xiY)] := 'Y';
+
+ MappableInputs.XInputButtonsNames[Ord(xiSelect)] := 'SELECT';
+ MappableInputs.XInputButtonsNames[Ord(xiUnused01)] := 'UNUSED01';
+ MappableInputs.XInputButtonsNames[Ord(xiStart)] := 'START';
+
+ MappableInputs.XInputButtonsNames[Ord(xiL1)] := 'L1';
+ MappableInputs.XInputButtonsNames[Ord(xiL2)] := 'L2';
+ MappableInputs.XInputButtonsNames[Ord(xiL3)] := 'L3';
+
+ MappableInputs.XInputButtonsNames[Ord(xiR1)] := 'R1';
+ MappableInputs.XInputButtonsNames[Ord(xiR2)] := 'R2';
+ MappableInputs.XInputButtonsNames[Ord(xiR3)] := 'R3';
+
+ // Default mapping
+ MappableInputs.XInputEnabled := True;
+ MappableInputs.XInputDeadzoneLeft := XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE;
+ MappableInputs.XInputDeadzoneRight := XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE;
+ MappableInputs.XInputDeadzoneTrigger := XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
+ MappableInputs.SetXInputMapping(miUnbound, xiUnbound);
+ MappableInputs.SetXInputMapping(miLJoyUp, xiLJoyUp);
+ MappableInputs.SetXInputMapping(miLJoyDown, xiLJoyDown);
+ MappableInputs.SetXInputMapping(miLJoyLeft, xiLJoyLeft);
+ MappableInputs.SetXInputMapping(miLJoyRight, xiLJoyRight);
+ MappableInputs.SetXInputMapping(miRJoyUp, xiRJoyUp);
+ MappableInputs.SetXInputMapping(miRJoyDown, xiRJoyDown);
+ MappableInputs.SetXInputMapping(miRJoyLeft, xiRJoyLeft);
+ MappableInputs.SetXInputMapping(miRJoyRight, xiRJoyRight);
+ MappableInputs.SetXInputMapping(miDPadUp, xiDPadUp);
+ MappableInputs.SetXInputMapping(miDPadDown, xiDPadDown);
+ MappableInputs.SetXInputMapping(miDPadLeft, xiDPadLeft);
+ MappableInputs.SetXInputMapping(miDPadRight, xiDPadRight);
+ MappableInputs.SetXInputMapping(miCross, xiA);
+ MappableInputs.SetXInputMapping(miCircle, xiB);
+ MappableInputs.SetXInputMapping(miSquare, xiX);
+ MappableInputs.SetXInputMapping(miTriangle, xiY);
+ MappableInputs.SetXInputMapping(miShare, xiUnbound);
+ MappableInputs.SetXInputMapping(miTouchPad, xiSelect);
+ MappableInputs.SetXInputMapping(miOptions, xiStart);
+ MappableInputs.SetXInputMapping(miL1, xiL1);
+ MappableInputs.SetXInputMapping(miL2, xiL2);
+ MappableInputs.SetXInputMapping(miL3, xiL3);
+ MappableInputs.SetXInputMapping(miR1, xiR1);
+ MappableInputs.SetXInputMapping(miR2, xiR2);
+ MappableInputs.SetXInputMapping(miR3, xiR3);
+
+ MappableInputs.LoadFromFile(XINPUT_CONFIG_FILE);
+end.
diff --git a/src/ps4_libscepad.pas b/src/ps4_libscepad.pas
index 2f4bf084..05013830 100644
--- a/src/ps4_libscepad.pas
+++ b/src/ps4_libscepad.pas
@@ -11,12 +11,13 @@ uses
sys_signal,
Classes,
SysUtils,
- xinput;
+ xinput,
+ formController;
implementation
uses
- ps4_libSceVideoOut;
+ ps4_libSceVideoOut, uMappableInputs;
const
SCE_PAD_ERROR_INVALID_ARG =-2137915391; // 0x80920001
@@ -313,7 +314,7 @@ end;
function ps4_scePadReadState(handle:Integer;data:PScePadData):Integer; SysV_ABI_CDecl;
var
mPoint,delta:TPoint;
- controllerState:TXInputState;
+ cs:TXInputState;
controllerIndex,stateResult:DWORD;
function GetAsyncKeyState(vKey:longint):Boolean; inline;
@@ -354,7 +355,7 @@ begin
if GetTickCount64 > xinput_last_poll + 10000 then
begin
for controllerIndex := 0 to XUSER_MAX_COUNT - 1 do
- xinput_controllers_connected[controllerIndex] := XInputGetState(controllerIndex, controllerState) <> ERROR_DEVICE_NOT_CONNECTED;
+ xinput_controllers_connected[controllerIndex] := XInputGetState(controllerIndex, cs) <> ERROR_DEVICE_NOT_CONNECTED;
xinput_last_poll := GetTickCount64;
end;
@@ -362,63 +363,58 @@ begin
// xinput for controllers
for controllerIndex := 0 to XUSER_MAX_COUNT - 1 do
begin
+ if not MappableInputs.XInputEnabled then break;
if not xinput_controllers_connected[controllerIndex] then
continue;
- ZeroMemory(@controllerState, SizeOf(controllerState));
- stateResult := XInputGetState(controllerIndex, controllerState);
+ ZeroMemory(@cs, SizeOf(cs));
+ stateResult := XInputGetState(controllerIndex, cs);
if stateResult = ERROR_SUCCESS then
begin
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_A) <> 0 then
+ if MappableInputs.PS4IsPressed(miCross, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_CROSS;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_B) <> 0 then
+ if MappableInputs.PS4IsPressed(miCircle, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_CIRCLE;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_X) <> 0 then
+ if MappableInputs.PS4IsPressed(miSquare, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_SQUARE;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_Y) <> 0 then
+ if MappableInputs.PS4IsPressed(miTriangle, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_TRIANGLE;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_START) <> 0 then
+ if MappableInputs.PS4IsPressed(miOptions, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_OPTIONS;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_BACK) <> 0 then
+ if MappableInputs.PS4IsPressed(miTouchPad, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_TOUCH_PAD;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_LEFT_SHOULDER) <> 0 then
+ if MappableInputs.PS4IsPressed(miL1, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_L1;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_RIGHT_SHOULDER) <> 0 then
+ if MappableInputs.PS4IsPressed(miR1, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_R1;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_LEFT_THUMB) <> 0 then
+ if MappableInputs.PS4IsPressed(miL3, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_L3;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_RIGHT_THUMB) <> 0 then
+ if MappableInputs.PS4IsPressed(miR3, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_R3;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_UP) <> 0 then
+ if MappableInputs.PS4IsPressed(miDPadUp, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_UP;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_DOWN) <> 0 then
+ if MappableInputs.PS4IsPressed(miDPadDown, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_DOWN;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_LEFT) <> 0 then
+ if MappableInputs.PS4IsPressed(miDPadLeft, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_LEFT;
- if (controllerState.Gamepad.wButtons and XINPUT_GAMEPAD_DPAD_RIGHT) <> 0 then
+ if MappableInputs.PS4IsPressed(miDPadRight, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_RIGHT;
- if (Abs(controllerState.Gamepad.sThumbLX) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) then
- data^.leftStick.x:=Trunc(((controllerState.Gamepad.sThumbLX / 32767.0)*0.5+0.5)*255);
- if (Abs(controllerState.Gamepad.sThumbLY) > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE) then
- data^.leftStick.y:=Trunc(255-((controllerState.Gamepad.sThumbLY / 32767.0)*0.5+0.5)*255);
+ data^.leftStick.x:=Trunc(128+(MappableInputs.GetAnalog(miLJoyRight, cs)-MappableInputs.GetAnalog(miLJoyLeft, cs))*127);
+ data^.leftStick.y:=Trunc(128+(MappableInputs.GetAnalog(miLJoyDown, cs)-MappableInputs.GetAnalog(miLJoyUp, cs))*127);
- if (Abs(controllerState.Gamepad.sThumbRX) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) then
- data^.rightStick.x:=Trunc(((controllerState.Gamepad.sThumbRX / 32767.0)*0.5+0.5)*255);
- if (Abs(controllerState.Gamepad.sThumbRY) > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE) then
- data^.rightStick.y:=Trunc(255-((controllerState.Gamepad.sThumbRY / 32767.0)*0.5+0.5)*255);
+ data^.rightStick.x:=Trunc(128+(MappableInputs.GetAnalog(miRJoyRight, cs)-MappableInputs.GetAnalog(miRJoyLeft, cs))*127);
+ data^.rightStick.y:=Trunc(128+(MappableInputs.GetAnalog(miLJoyDown, cs)-MappableInputs.GetAnalog(miLJoyUp, cs))*127);
- if (controllerState.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) then
- data^.analogButtons.l2:=controllerState.Gamepad.bLeftTrigger;
- if (controllerState.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD) then
- data^.analogButtons.r2:=controllerState.Gamepad.bRightTrigger;
+ data^.analogButtons.l2:=Trunc(MappableInputs.GetAnalog(miL2, cs)*255);
+ data^.analogButtons.r2:=Trunc(MappableInputs.GetAnalog(miR2, cs)*255);
- if (controllerState.Gamepad.bLeftTrigger > 250) then
+ if MappableInputs.PS4IsPressed(miL2, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_L2;
- if (controllerState.Gamepad.bRightTrigger > 250) then
+ if MappableInputs.PS4IsPressed(miR2, cs) then
data^.buttons:=data^.buttons or SCE_PAD_BUTTON_R2;
end;
end;
diff --git a/src/ps4_libscevideoout.pas b/src/ps4_libscevideoout.pas
index d90f5a50..e9370beb 100644
--- a/src/ps4_libscevideoout.pas
+++ b/src/ps4_libscevideoout.pas
@@ -257,7 +257,8 @@ uses
ps4_time,
spinlock,
hamt,
- param_sfo;
+ param_sfo,
+ formController;
type
PQNode=^TQNode;
@@ -542,6 +543,11 @@ begin
UnlockRealizeBounds;
end;
+
+ if Key = VK_ESCAPE then
+ begin
+ FormControllers.Show;
+ end;
end;
procedure TVideoOut.OnVblank(Sender:TObject);
@@ -585,10 +591,13 @@ begin
FForm.SetCaptionFPS(0);
FForm.OnClose:=@FForm.CloseEvent;
FForm.OnKeyDown:=@FForm.KeyEvent;
+ FForm.Position:=poScreenCenter;
Application.UpdateMainForm(FForm);
FForm.Show;
+ Application.CreateForm(TFormControllers, FormControllers);
+
FGpuFlip:=TvFlip.Create(FForm.Handle);
FGpuFlip.FNeoMode:=ps4_sceKernelIsNeoMode<>0;