summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common')
-rw-r--r--src/input_common/CMakeLists.txt4
-rw-r--r--src/input_common/main.cpp64
-rw-r--r--src/input_common/main.h39
-rw-r--r--src/input_common/mouse/mouse_poller.cpp1
-rw-r--r--src/input_common/sdl/sdl_impl.cpp117
-rw-r--r--src/input_common/tas/tas_input.cpp455
-rw-r--r--src/input_common/tas/tas_input.h237
-rw-r--r--src/input_common/tas/tas_poller.cpp101
-rw-r--r--src/input_common/tas/tas_poller.h43
-rw-r--r--src/input_common/udp/client.cpp74
-rw-r--r--src/input_common/udp/client.h2
11 files changed, 1053 insertions, 84 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index c4283a952..dd13d948f 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -21,6 +21,10 @@ add_library(input_common STATIC
21 mouse/mouse_poller.h 21 mouse/mouse_poller.h
22 sdl/sdl.cpp 22 sdl/sdl.cpp
23 sdl/sdl.h 23 sdl/sdl.h
24 tas/tas_input.cpp
25 tas/tas_input.h
26 tas/tas_poller.cpp
27 tas/tas_poller.h
24 udp/client.cpp 28 udp/client.cpp
25 udp/client.h 29 udp/client.h
26 udp/protocol.cpp 30 udp/protocol.cpp
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 8de3d4520..f3907c65a 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -5,6 +5,7 @@
5#include <memory> 5#include <memory>
6#include <thread> 6#include <thread>
7#include "common/param_package.h" 7#include "common/param_package.h"
8#include "common/settings.h"
8#include "input_common/analog_from_button.h" 9#include "input_common/analog_from_button.h"
9#include "input_common/gcadapter/gc_adapter.h" 10#include "input_common/gcadapter/gc_adapter.h"
10#include "input_common/gcadapter/gc_poller.h" 11#include "input_common/gcadapter/gc_poller.h"
@@ -13,6 +14,8 @@
13#include "input_common/motion_from_button.h" 14#include "input_common/motion_from_button.h"
14#include "input_common/mouse/mouse_input.h" 15#include "input_common/mouse/mouse_input.h"
15#include "input_common/mouse/mouse_poller.h" 16#include "input_common/mouse/mouse_poller.h"
17#include "input_common/tas/tas_input.h"
18#include "input_common/tas/tas_poller.h"
16#include "input_common/touch_from_button.h" 19#include "input_common/touch_from_button.h"
17#include "input_common/udp/client.h" 20#include "input_common/udp/client.h"
18#include "input_common/udp/udp.h" 21#include "input_common/udp/udp.h"
@@ -60,6 +63,12 @@ struct InputSubsystem::Impl {
60 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion); 63 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
61 mousetouch = std::make_shared<MouseTouchFactory>(mouse); 64 mousetouch = std::make_shared<MouseTouchFactory>(mouse);
62 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch); 65 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
66
67 tas = std::make_shared<TasInput::Tas>();
68 tasbuttons = std::make_shared<TasButtonFactory>(tas);
69 Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
70 tasanalog = std::make_shared<TasAnalogFactory>(tas);
71 Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
63 } 72 }
64 73
65 void Shutdown() { 74 void Shutdown() {
@@ -94,6 +103,12 @@ struct InputSubsystem::Impl {
94 mouseanalog.reset(); 103 mouseanalog.reset();
95 mousemotion.reset(); 104 mousemotion.reset();
96 mousetouch.reset(); 105 mousetouch.reset();
106
107 Input::UnregisterFactory<Input::ButtonDevice>("tas");
108 Input::UnregisterFactory<Input::AnalogDevice>("tas");
109
110 tasbuttons.reset();
111 tasanalog.reset();
97 } 112 }
98 113
99 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { 114 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
@@ -101,6 +116,10 @@ struct InputSubsystem::Impl {
101 Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, 116 Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
102 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, 117 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
103 }; 118 };
119 if (Settings::values.tas_enable) {
120 devices.emplace_back(
121 Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}});
122 }
104#ifdef HAVE_SDL2 123#ifdef HAVE_SDL2
105 auto sdl_devices = sdl->GetInputDevices(); 124 auto sdl_devices = sdl->GetInputDevices();
106 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); 125 devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
@@ -120,6 +139,9 @@ struct InputSubsystem::Impl {
120 if (params.Get("class", "") == "gcpad") { 139 if (params.Get("class", "") == "gcpad") {
121 return gcadapter->GetAnalogMappingForDevice(params); 140 return gcadapter->GetAnalogMappingForDevice(params);
122 } 141 }
142 if (params.Get("class", "") == "tas") {
143 return tas->GetAnalogMappingForDevice(params);
144 }
123#ifdef HAVE_SDL2 145#ifdef HAVE_SDL2
124 if (params.Get("class", "") == "sdl") { 146 if (params.Get("class", "") == "sdl") {
125 return sdl->GetAnalogMappingForDevice(params); 147 return sdl->GetAnalogMappingForDevice(params);
@@ -136,6 +158,9 @@ struct InputSubsystem::Impl {
136 if (params.Get("class", "") == "gcpad") { 158 if (params.Get("class", "") == "gcpad") {
137 return gcadapter->GetButtonMappingForDevice(params); 159 return gcadapter->GetButtonMappingForDevice(params);
138 } 160 }
161 if (params.Get("class", "") == "tas") {
162 return tas->GetButtonMappingForDevice(params);
163 }
139#ifdef HAVE_SDL2 164#ifdef HAVE_SDL2
140 if (params.Get("class", "") == "sdl") { 165 if (params.Get("class", "") == "sdl") {
141 return sdl->GetButtonMappingForDevice(params); 166 return sdl->GetButtonMappingForDevice(params);
@@ -174,9 +199,12 @@ struct InputSubsystem::Impl {
174 std::shared_ptr<MouseAnalogFactory> mouseanalog; 199 std::shared_ptr<MouseAnalogFactory> mouseanalog;
175 std::shared_ptr<MouseMotionFactory> mousemotion; 200 std::shared_ptr<MouseMotionFactory> mousemotion;
176 std::shared_ptr<MouseTouchFactory> mousetouch; 201 std::shared_ptr<MouseTouchFactory> mousetouch;
202 std::shared_ptr<TasButtonFactory> tasbuttons;
203 std::shared_ptr<TasAnalogFactory> tasanalog;
177 std::shared_ptr<CemuhookUDP::Client> udp; 204 std::shared_ptr<CemuhookUDP::Client> udp;
178 std::shared_ptr<GCAdapter::Adapter> gcadapter; 205 std::shared_ptr<GCAdapter::Adapter> gcadapter;
179 std::shared_ptr<MouseInput::Mouse> mouse; 206 std::shared_ptr<MouseInput::Mouse> mouse;
207 std::shared_ptr<TasInput::Tas> tas;
180}; 208};
181 209
182InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} 210InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -207,6 +235,14 @@ const MouseInput::Mouse* InputSubsystem::GetMouse() const {
207 return impl->mouse.get(); 235 return impl->mouse.get();
208} 236}
209 237
238TasInput::Tas* InputSubsystem::GetTas() {
239 return impl->tas.get();
240}
241
242const TasInput::Tas* InputSubsystem::GetTas() const {
243 return impl->tas.get();
244}
245
210std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { 246std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
211 return impl->GetInputDevices(); 247 return impl->GetInputDevices();
212} 248}
@@ -287,6 +323,22 @@ const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
287 return impl->mousetouch.get(); 323 return impl->mousetouch.get();
288} 324}
289 325
326TasButtonFactory* InputSubsystem::GetTasButtons() {
327 return impl->tasbuttons.get();
328}
329
330const TasButtonFactory* InputSubsystem::GetTasButtons() const {
331 return impl->tasbuttons.get();
332}
333
334TasAnalogFactory* InputSubsystem::GetTasAnalogs() {
335 return impl->tasanalog.get();
336}
337
338const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const {
339 return impl->tasanalog.get();
340}
341
290void InputSubsystem::ReloadInputDevices() { 342void InputSubsystem::ReloadInputDevices() {
291 if (!impl->udp) { 343 if (!impl->udp) {
292 return; 344 return;
@@ -294,8 +346,8 @@ void InputSubsystem::ReloadInputDevices() {
294 impl->udp->ReloadSockets(); 346 impl->udp->ReloadSockets();
295} 347}
296 348
297std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers([ 349std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers(
298 [maybe_unused]] Polling::DeviceType type) const { 350 [[maybe_unused]] Polling::DeviceType type) const {
299#ifdef HAVE_SDL2 351#ifdef HAVE_SDL2
300 return impl->sdl->GetPollers(type); 352 return impl->sdl->GetPollers(type);
301#else 353#else
@@ -304,10 +356,10 @@ std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers([
304} 356}
305 357
306std::string GenerateKeyboardParam(int key_code) { 358std::string GenerateKeyboardParam(int key_code) {
307 Common::ParamPackage param{ 359 Common::ParamPackage param;
308 {"engine", "keyboard"}, 360 param.Set("engine", "keyboard");
309 {"code", std::to_string(key_code)}, 361 param.Set("code", key_code);
310 }; 362 param.Set("toggle", false);
311 return param.Serialize(); 363 return param.Serialize();
312} 364}
313 365
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 5d6f26385..6390d3f09 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -29,6 +29,10 @@ namespace MouseInput {
29class Mouse; 29class Mouse;
30} 30}
31 31
32namespace TasInput {
33class Tas;
34}
35
32namespace InputCommon { 36namespace InputCommon {
33namespace Polling { 37namespace Polling {
34 38
@@ -64,6 +68,8 @@ class MouseButtonFactory;
64class MouseAnalogFactory; 68class MouseAnalogFactory;
65class MouseMotionFactory; 69class MouseMotionFactory;
66class MouseTouchFactory; 70class MouseTouchFactory;
71class TasButtonFactory;
72class TasAnalogFactory;
67class Keyboard; 73class Keyboard;
68 74
69/** 75/**
@@ -103,6 +109,11 @@ public:
103 /// Retrieves the underlying mouse device. 109 /// Retrieves the underlying mouse device.
104 [[nodiscard]] const MouseInput::Mouse* GetMouse() const; 110 [[nodiscard]] const MouseInput::Mouse* GetMouse() const;
105 111
112 /// Retrieves the underlying tas device.
113 [[nodiscard]] TasInput::Tas* GetTas();
114
115 /// Retrieves the underlying tas device.
116 [[nodiscard]] const TasInput::Tas* GetTas() const;
106 /** 117 /**
107 * Returns all available input devices that this Factory can create a new device with. 118 * Returns all available input devices that this Factory can create a new device with.
108 * Each returned ParamPackage should have a `display` field used for display, a class field for 119 * Each returned ParamPackage should have a `display` field used for display, a class field for
@@ -144,30 +155,42 @@ public:
144 /// Retrieves the underlying udp touch handler. 155 /// Retrieves the underlying udp touch handler.
145 [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; 156 [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const;
146 157
147 /// Retrieves the underlying GameCube button handler. 158 /// Retrieves the underlying mouse button handler.
148 [[nodiscard]] MouseButtonFactory* GetMouseButtons(); 159 [[nodiscard]] MouseButtonFactory* GetMouseButtons();
149 160
150 /// Retrieves the underlying GameCube button handler. 161 /// Retrieves the underlying mouse button handler.
151 [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const; 162 [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const;
152 163
153 /// Retrieves the underlying udp touch handler. 164 /// Retrieves the underlying mouse analog handler.
154 [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs(); 165 [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs();
155 166
156 /// Retrieves the underlying udp touch handler. 167 /// Retrieves the underlying mouse analog handler.
157 [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const; 168 [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const;
158 169
159 /// Retrieves the underlying udp motion handler. 170 /// Retrieves the underlying mouse motion handler.
160 [[nodiscard]] MouseMotionFactory* GetMouseMotions(); 171 [[nodiscard]] MouseMotionFactory* GetMouseMotions();
161 172
162 /// Retrieves the underlying udp motion handler. 173 /// Retrieves the underlying mouse motion handler.
163 [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const; 174 [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const;
164 175
165 /// Retrieves the underlying udp touch handler. 176 /// Retrieves the underlying mouse touch handler.
166 [[nodiscard]] MouseTouchFactory* GetMouseTouch(); 177 [[nodiscard]] MouseTouchFactory* GetMouseTouch();
167 178
168 /// Retrieves the underlying udp touch handler. 179 /// Retrieves the underlying mouse touch handler.
169 [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; 180 [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const;
170 181
182 /// Retrieves the underlying tas button handler.
183 [[nodiscard]] TasButtonFactory* GetTasButtons();
184
185 /// Retrieves the underlying tas button handler.
186 [[nodiscard]] const TasButtonFactory* GetTasButtons() const;
187
188 /// Retrieves the underlying tas analogs handler.
189 [[nodiscard]] TasAnalogFactory* GetTasAnalogs();
190
191 /// Retrieves the underlying tas analogs handler.
192 [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const;
193
171 /// Reloads the input devices 194 /// Reloads the input devices
172 void ReloadInputDevices(); 195 void ReloadInputDevices();
173 196
diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp
index efcdd85d2..090b26972 100644
--- a/src/input_common/mouse/mouse_poller.cpp
+++ b/src/input_common/mouse/mouse_poller.cpp
@@ -57,6 +57,7 @@ Common::ParamPackage MouseButtonFactory::GetNextInput() const {
57 if (pad.button != MouseInput::MouseButton::Undefined) { 57 if (pad.button != MouseInput::MouseButton::Undefined) {
58 params.Set("engine", "mouse"); 58 params.Set("engine", "mouse");
59 params.Set("button", static_cast<u16>(pad.button)); 59 params.Set("button", static_cast<u16>(pad.button));
60 params.Set("toggle", false);
60 return params; 61 return params;
61 } 62 }
62 } 63 }
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index 70a0ba09c..ecb00d428 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -21,7 +21,7 @@
21#include "common/logging/log.h" 21#include "common/logging/log.h"
22#include "common/math_util.h" 22#include "common/math_util.h"
23#include "common/param_package.h" 23#include "common/param_package.h"
24#include "common/settings_input.h" 24#include "common/settings.h"
25#include "common/threadsafe_queue.h" 25#include "common/threadsafe_queue.h"
26#include "core/frontend/input.h" 26#include "core/frontend/input.h"
27#include "input_common/motion_input.h" 27#include "input_common/motion_input.h"
@@ -82,6 +82,12 @@ public:
82 state.buttons.insert_or_assign(button, value); 82 state.buttons.insert_or_assign(button, value);
83 } 83 }
84 84
85 void PreSetButton(int button) {
86 if (!state.buttons.contains(button)) {
87 SetButton(button, false);
88 }
89 }
90
85 void SetMotion(SDL_ControllerSensorEvent event) { 91 void SetMotion(SDL_ControllerSensorEvent event) {
86 constexpr float gravity_constant = 9.80665f; 92 constexpr float gravity_constant = 9.80665f;
87 std::lock_guard lock{mutex}; 93 std::lock_guard lock{mutex};
@@ -155,9 +161,17 @@ public:
155 state.axes.insert_or_assign(axis, value); 161 state.axes.insert_or_assign(axis, value);
156 } 162 }
157 163
158 float GetAxis(int axis, float range) const { 164 void PreSetAxis(int axis) {
165 if (!state.axes.contains(axis)) {
166 SetAxis(axis, 0);
167 }
168 }
169
170 float GetAxis(int axis, float range, float offset) const {
159 std::lock_guard lock{mutex}; 171 std::lock_guard lock{mutex};
160 return static_cast<float>(state.axes.at(axis)) / (32767.0f * range); 172 const float value = static_cast<float>(state.axes.at(axis)) / 32767.0f;
173 const float offset_scale = (value + offset) > 0.0f ? 1.0f + offset : 1.0f - offset;
174 return (value + offset) / range / offset_scale;
161 } 175 }
162 176
163 bool RumblePlay(u16 amp_low, u16 amp_high) { 177 bool RumblePlay(u16 amp_low, u16 amp_high) {
@@ -174,9 +188,10 @@ public:
174 return false; 188 return false;
175 } 189 }
176 190
177 std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const { 191 std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range, float offset_x,
178 float x = GetAxis(axis_x, range); 192 float offset_y) const {
179 float y = GetAxis(axis_y, range); 193 float x = GetAxis(axis_x, range, offset_x);
194 float y = GetAxis(axis_y, range, offset_y);
180 y = -y; // 3DS uses an y-axis inverse from SDL 195 y = -y; // 3DS uses an y-axis inverse from SDL
181 196
182 // Make sure the coordinates are in the unit circle, 197 // Make sure the coordinates are in the unit circle,
@@ -240,11 +255,25 @@ public:
240 } 255 }
241 256
242 bool IsJoyconLeft() const { 257 bool IsJoyconLeft() const {
243 return std::strstr(GetControllerName().c_str(), "Joy-Con Left") != nullptr; 258 const std::string controller_name = GetControllerName();
259 if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) {
260 return true;
261 }
262 if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) {
263 return true;
264 }
265 return false;
244 } 266 }
245 267
246 bool IsJoyconRight() const { 268 bool IsJoyconRight() const {
247 return std::strstr(GetControllerName().c_str(), "Joy-Con Right") != nullptr; 269 const std::string controller_name = GetControllerName();
270 if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) {
271 return true;
272 }
273 if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) {
274 return true;
275 }
276 return false;
248 } 277 }
249 278
250 std::string GetControllerName() const { 279 std::string GetControllerName() const {
@@ -483,7 +512,7 @@ public:
483 trigger_if_greater(trigger_if_greater_) {} 512 trigger_if_greater(trigger_if_greater_) {}
484 513
485 bool GetStatus() const override { 514 bool GetStatus() const override {
486 const float axis_value = joystick->GetAxis(axis, 1.0f); 515 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
487 if (trigger_if_greater) { 516 if (trigger_if_greater) {
488 return axis_value > threshold; 517 return axis_value > threshold;
489 } 518 }
@@ -500,12 +529,14 @@ private:
500class SDLAnalog final : public Input::AnalogDevice { 529class SDLAnalog final : public Input::AnalogDevice {
501public: 530public:
502 explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, 531 explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_,
503 bool invert_x_, bool invert_y_, float deadzone_, float range_) 532 bool invert_x_, bool invert_y_, float deadzone_, float range_,
533 float offset_x_, float offset_y_)
504 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), 534 : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_),
505 invert_y(invert_y_), deadzone(deadzone_), range(range_) {} 535 invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_),
536 offset_y(offset_y_) {}
506 537
507 std::tuple<float, float> GetStatus() const override { 538 std::tuple<float, float> GetStatus() const override {
508 auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range); 539 auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y);
509 const float r = std::sqrt((x * x) + (y * y)); 540 const float r = std::sqrt((x * x) + (y * y));
510 if (invert_x) { 541 if (invert_x) {
511 x = -x; 542 x = -x;
@@ -522,8 +553,8 @@ public:
522 } 553 }
523 554
524 std::tuple<float, float> GetRawStatus() const override { 555 std::tuple<float, float> GetRawStatus() const override {
525 const float x = joystick->GetAxis(axis_x, range); 556 const float x = joystick->GetAxis(axis_x, range, offset_x);
526 const float y = joystick->GetAxis(axis_y, range); 557 const float y = joystick->GetAxis(axis_y, range, offset_y);
527 return {x, -y}; 558 return {x, -y};
528 } 559 }
529 560
@@ -555,6 +586,8 @@ private:
555 const bool invert_y; 586 const bool invert_y;
556 const float deadzone; 587 const float deadzone;
557 const float range; 588 const float range;
589 const float offset_x;
590 const float offset_y;
558}; 591};
559 592
560class SDLVibration final : public Input::VibrationDevice { 593class SDLVibration final : public Input::VibrationDevice {
@@ -621,7 +654,7 @@ public:
621 trigger_if_greater(trigger_if_greater_) {} 654 trigger_if_greater(trigger_if_greater_) {}
622 655
623 Input::MotionStatus GetStatus() const override { 656 Input::MotionStatus GetStatus() const override {
624 const float axis_value = joystick->GetAxis(axis, 1.0f); 657 const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f);
625 bool trigger = axis_value < threshold; 658 bool trigger = axis_value < threshold;
626 if (trigger_if_greater) { 659 if (trigger_if_greater) {
627 trigger = axis_value > threshold; 660 trigger = axis_value > threshold;
@@ -720,13 +753,13 @@ public:
720 LOG_ERROR(Input, "Unknown direction {}", direction_name); 753 LOG_ERROR(Input, "Unknown direction {}", direction_name);
721 } 754 }
722 // This is necessary so accessing GetAxis with axis won't crash 755 // This is necessary so accessing GetAxis with axis won't crash
723 joystick->SetAxis(axis, 0); 756 joystick->PreSetAxis(axis);
724 return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); 757 return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
725 } 758 }
726 759
727 const int button = params.Get("button", 0); 760 const int button = params.Get("button", 0);
728 // This is necessary so accessing GetButton with button won't crash 761 // This is necessary so accessing GetButton with button won't crash
729 joystick->SetButton(button, false); 762 joystick->PreSetButton(button);
730 return std::make_unique<SDLButton>(joystick, button, toggle); 763 return std::make_unique<SDLButton>(joystick, button, toggle);
731 } 764 }
732 765
@@ -757,13 +790,15 @@ public:
757 const std::string invert_y_value = params.Get("invert_y", "+"); 790 const std::string invert_y_value = params.Get("invert_y", "+");
758 const bool invert_x = invert_x_value == "-"; 791 const bool invert_x = invert_x_value == "-";
759 const bool invert_y = invert_y_value == "-"; 792 const bool invert_y = invert_y_value == "-";
793 const float offset_x = std::clamp(params.Get("offset_x", 0.0f), -0.99f, 0.99f);
794 const float offset_y = std::clamp(params.Get("offset_y", 0.0f), -0.99f, 0.99f);
760 auto joystick = state.GetSDLJoystickByGUID(guid, port); 795 auto joystick = state.GetSDLJoystickByGUID(guid, port);
761 796
762 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash 797 // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
763 joystick->SetAxis(axis_x, 0); 798 joystick->PreSetAxis(axis_x);
764 joystick->SetAxis(axis_y, 0); 799 joystick->PreSetAxis(axis_y);
765 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone, 800 return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone,
766 range); 801 range, offset_x, offset_y);
767 } 802 }
768 803
769private: 804private:
@@ -844,13 +879,13 @@ public:
844 LOG_ERROR(Input, "Unknown direction {}", direction_name); 879 LOG_ERROR(Input, "Unknown direction {}", direction_name);
845 } 880 }
846 // This is necessary so accessing GetAxis with axis won't crash 881 // This is necessary so accessing GetAxis with axis won't crash
847 joystick->SetAxis(axis, 0); 882 joystick->PreSetAxis(axis);
848 return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater); 883 return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
849 } 884 }
850 885
851 const int button = params.Get("button", 0); 886 const int button = params.Get("button", 0);
852 // This is necessary so accessing GetButton with button won't crash 887 // This is necessary so accessing GetButton with button won't crash
853 joystick->SetButton(button, false); 888 joystick->PreSetButton(button);
854 return std::make_unique<SDLButtonMotion>(joystick, button); 889 return std::make_unique<SDLButtonMotion>(joystick, button);
855 } 890 }
856 891
@@ -869,16 +904,21 @@ SDLState::SDLState() {
869 RegisterFactory<VibrationDevice>("sdl", vibration_factory); 904 RegisterFactory<VibrationDevice>("sdl", vibration_factory);
870 RegisterFactory<MotionDevice>("sdl", motion_factory); 905 RegisterFactory<MotionDevice>("sdl", motion_factory);
871 906
907 if (!Settings::values.enable_raw_input) {
908 // Disable raw input. When enabled this setting causes SDL to die when a web applet opens
909 SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0");
910 }
911
872 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers 912 // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers
873 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); 913 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
874 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); 914 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
875 915
876 // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a 916 // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a
877 // GameController and not a generic one 917 // GameController and not a generic one
878 SDL_SetHint("SDL_JOYSTICK_HIDAPI_JOY_CONS", "1"); 918 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
879 919
880 // Turn off Pro controller home led 920 // Turn off Pro controller home led
881 SDL_SetHint("SDL_JOYSTICK_HIDAPI_SWITCH_HOME_LED", "0"); 921 SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
882 922
883 // If the frontend is going to manage the event loop, then we don't start one here 923 // If the frontend is going to manage the event loop, then we don't start one here
884 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0; 924 start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0;
@@ -995,6 +1035,7 @@ Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid
995 params.Set("port", port); 1035 params.Set("port", port);
996 params.Set("guid", std::move(guid)); 1036 params.Set("guid", std::move(guid));
997 params.Set("button", button); 1037 params.Set("button", button);
1038 params.Set("toggle", false);
998 return params; 1039 return params;
999} 1040}
1000 1041
@@ -1134,13 +1175,15 @@ Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& gu
1134} 1175}
1135 1176
1136Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x, 1177Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x,
1137 int axis_y) { 1178 int axis_y, float offset_x, float offset_y) {
1138 Common::ParamPackage params; 1179 Common::ParamPackage params;
1139 params.Set("engine", "sdl"); 1180 params.Set("engine", "sdl");
1140 params.Set("port", port); 1181 params.Set("port", port);
1141 params.Set("guid", guid); 1182 params.Set("guid", guid);
1142 params.Set("axis_x", axis_x); 1183 params.Set("axis_x", axis_x);
1143 params.Set("axis_y", axis_y); 1184 params.Set("axis_y", axis_y);
1185 params.Set("offset_x", offset_x);
1186 params.Set("offset_y", offset_y);
1144 params.Set("invert_x", "+"); 1187 params.Set("invert_x", "+");
1145 params.Set("invert_y", "+"); 1188 params.Set("invert_y", "+");
1146 return params; 1189 return params;
@@ -1342,24 +1385,39 @@ AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& pa
1342 const auto& binding_left_y = 1385 const auto& binding_left_y =
1343 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); 1386 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
1344 if (params.Has("guid2")) { 1387 if (params.Has("guid2")) {
1388 joystick2->PreSetAxis(binding_left_x.value.axis);
1389 joystick2->PreSetAxis(binding_left_y.value.axis);
1390 const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1391 const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1345 mapping.insert_or_assign( 1392 mapping.insert_or_assign(
1346 Settings::NativeAnalog::LStick, 1393 Settings::NativeAnalog::LStick,
1347 BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(), 1394 BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(),
1348 binding_left_x.value.axis, binding_left_y.value.axis)); 1395 binding_left_x.value.axis, binding_left_y.value.axis,
1396 left_offset_x, left_offset_y));
1349 } else { 1397 } else {
1398 joystick->PreSetAxis(binding_left_x.value.axis);
1399 joystick->PreSetAxis(binding_left_y.value.axis);
1400 const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0);
1401 const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0);
1350 mapping.insert_or_assign( 1402 mapping.insert_or_assign(
1351 Settings::NativeAnalog::LStick, 1403 Settings::NativeAnalog::LStick,
1352 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), 1404 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1353 binding_left_x.value.axis, binding_left_y.value.axis)); 1405 binding_left_x.value.axis, binding_left_y.value.axis,
1406 left_offset_x, left_offset_y));
1354 } 1407 }
1355 const auto& binding_right_x = 1408 const auto& binding_right_x =
1356 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); 1409 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);
1357 const auto& binding_right_y = 1410 const auto& binding_right_y =
1358 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); 1411 SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);
1412 joystick->PreSetAxis(binding_right_x.value.axis);
1413 joystick->PreSetAxis(binding_right_y.value.axis);
1414 const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0);
1415 const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0);
1359 mapping.insert_or_assign(Settings::NativeAnalog::RStick, 1416 mapping.insert_or_assign(Settings::NativeAnalog::RStick,
1360 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), 1417 BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1361 binding_right_x.value.axis, 1418 binding_right_x.value.axis,
1362 binding_right_y.value.axis)); 1419 binding_right_y.value.axis, right_offset_x,
1420 right_offset_y));
1363 return mapping; 1421 return mapping;
1364} 1422}
1365 1423
@@ -1563,8 +1621,9 @@ public:
1563 } 1621 }
1564 1622
1565 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { 1623 if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) {
1624 // Set offset to zero since the joystick is not on center
1566 auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), 1625 auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(),
1567 first_axis, axis); 1626 first_axis, axis, 0, 0);
1568 first_axis = -1; 1627 first_axis = -1;
1569 return params; 1628 return params;
1570 } 1629 }
diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp
new file mode 100644
index 000000000..1598092b6
--- /dev/null
+++ b/src/input_common/tas/tas_input.cpp
@@ -0,0 +1,455 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <cstring>
6#include <regex>
7
8#include "common/fs/file.h"
9#include "common/fs/fs_types.h"
10#include "common/fs/path_util.h"
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/tas/tas_input.h"
14
15namespace TasInput {
16
17// Supported keywords and buttons from a TAS file
18constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
19 std::pair{"KEY_A", TasButton::BUTTON_A},
20 {"KEY_B", TasButton::BUTTON_B},
21 {"KEY_X", TasButton::BUTTON_X},
22 {"KEY_Y", TasButton::BUTTON_Y},
23 {"KEY_LSTICK", TasButton::STICK_L},
24 {"KEY_RSTICK", TasButton::STICK_R},
25 {"KEY_L", TasButton::TRIGGER_L},
26 {"KEY_R", TasButton::TRIGGER_R},
27 {"KEY_PLUS", TasButton::BUTTON_PLUS},
28 {"KEY_MINUS", TasButton::BUTTON_MINUS},
29 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
30 {"KEY_DUP", TasButton::BUTTON_UP},
31 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
32 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
33 {"KEY_SL", TasButton::BUTTON_SL},
34 {"KEY_SR", TasButton::BUTTON_SR},
35 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
36 {"KEY_HOME", TasButton::BUTTON_HOME},
37 {"KEY_ZL", TasButton::TRIGGER_ZL},
38 {"KEY_ZR", TasButton::TRIGGER_ZR},
39};
40
41Tas::Tas() {
42 if (!Settings::values.tas_enable) {
43 needs_reset = true;
44 return;
45 }
46 LoadTasFiles();
47}
48
49Tas::~Tas() {
50 Stop();
51};
52
53void Tas::LoadTasFiles() {
54 script_length = 0;
55 for (size_t i = 0; i < commands.size(); i++) {
56 LoadTasFile(i);
57 if (commands[i].size() > script_length) {
58 script_length = commands[i].size();
59 }
60 }
61}
62
63void Tas::LoadTasFile(size_t player_index) {
64 if (!commands[player_index].empty()) {
65 commands[player_index].clear();
66 }
67 std::string file =
68 Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
69 fmt::format("script0-{}.txt", player_index + 1),
70 Common::FS::FileType::BinaryFile);
71 std::stringstream command_line(file);
72 std::string line;
73 int frame_no = 0;
74 while (std::getline(command_line, line, '\n')) {
75 if (line.empty()) {
76 continue;
77 }
78 LOG_DEBUG(Input, "Loading line: {}", line);
79 std::smatch m;
80
81 std::stringstream linestream(line);
82 std::string segment;
83 std::vector<std::string> seglist;
84
85 while (std::getline(linestream, segment, ' ')) {
86 seglist.push_back(segment);
87 }
88
89 if (seglist.size() < 4) {
90 continue;
91 }
92
93 while (frame_no < std::stoi(seglist.at(0))) {
94 commands[player_index].push_back({});
95 frame_no++;
96 }
97
98 TASCommand command = {
99 .buttons = ReadCommandButtons(seglist.at(1)),
100 .l_axis = ReadCommandAxis(seglist.at(2)),
101 .r_axis = ReadCommandAxis(seglist.at(3)),
102 };
103 commands[player_index].push_back(command);
104 frame_no++;
105 }
106 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
107}
108
109void Tas::WriteTasFile(std::u8string file_name) {
110 std::string output_text;
111 for (size_t frame = 0; frame < record_commands.size(); frame++) {
112 if (!output_text.empty()) {
113 output_text += "\n";
114 }
115 const TASCommand& line = record_commands[frame];
116 output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
117 WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
118 }
119 const auto bytes_written = Common::FS::WriteStringToFile(
120 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
121 Common::FS::FileType::TextFile, output_text);
122 if (bytes_written == output_text.size()) {
123 LOG_INFO(Input, "TAS file written to file!");
124 } else {
125 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
126 output_text.size());
127 }
128}
129
130std::pair<float, float> Tas::FlipAxisY(std::pair<float, float> old) {
131 auto [x, y] = old;
132 return {x, -y};
133}
134
135void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) {
136 last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])};
137}
138
139std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
140 TasState state;
141 if (is_recording) {
142 return {TasState::Recording, 0, record_commands.size()};
143 }
144
145 if (is_running) {
146 state = TasState::Running;
147 } else {
148 state = TasState::Stopped;
149 }
150
151 return {state, current_command, script_length};
152}
153
154std::string Tas::DebugButtons(u32 buttons) const {
155 return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons));
156}
157
158std::string Tas::DebugJoystick(float x, float y) const {
159 return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y));
160}
161
162std::string Tas::DebugInput(const TasData& data) const {
163 return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons),
164 DebugJoystick(data.axis[0], data.axis[1]),
165 DebugJoystick(data.axis[2], data.axis[3]));
166}
167
168std::string Tas::DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const {
169 std::string returns = "[ ";
170 for (size_t i = 0; i < arr.size(); i++) {
171 returns += DebugInput(arr[i]);
172 if (i != arr.size() - 1) {
173 returns += " , ";
174 }
175 }
176 return returns + "]";
177}
178
179std::string Tas::ButtonsToString(u32 button) const {
180 std::string returns;
181 for (auto [text_button, tas_button] : text_to_tas_button) {
182 if ((button & static_cast<u32>(tas_button)) != 0)
183 returns += fmt::format(", {}", text_button.substr(4));
184 }
185 return returns.empty() ? "" : returns.substr(2);
186}
187
188void Tas::UpdateThread() {
189 if (!Settings::values.tas_enable) {
190 if (is_running) {
191 Stop();
192 }
193 return;
194 }
195
196 if (is_recording) {
197 record_commands.push_back(last_input);
198 }
199 if (needs_reset) {
200 current_command = 0;
201 needs_reset = false;
202 LoadTasFiles();
203 LOG_DEBUG(Input, "tas_reset done");
204 }
205
206 if (!is_running) {
207 tas_data.fill({});
208 return;
209 }
210 if (current_command < script_length) {
211 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
212 size_t frame = current_command++;
213 for (size_t i = 0; i < commands.size(); i++) {
214 if (frame < commands[i].size()) {
215 TASCommand command = commands[i][frame];
216 tas_data[i].buttons = command.buttons;
217 auto [l_axis_x, l_axis_y] = command.l_axis;
218 tas_data[i].axis[0] = l_axis_x;
219 tas_data[i].axis[1] = l_axis_y;
220 auto [r_axis_x, r_axis_y] = command.r_axis;
221 tas_data[i].axis[2] = r_axis_x;
222 tas_data[i].axis[3] = r_axis_y;
223 } else {
224 tas_data[i] = {};
225 }
226 }
227 } else {
228 is_running = Settings::values.tas_loop.GetValue();
229 current_command = 0;
230 tas_data.fill({});
231 if (!is_running) {
232 SwapToStoredController();
233 }
234 }
235 LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
236}
237
238TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
239 std::stringstream linestream(line);
240 std::string segment;
241 std::vector<std::string> seglist;
242
243 while (std::getline(linestream, segment, ';')) {
244 seglist.push_back(segment);
245 }
246
247 const float x = std::stof(seglist.at(0)) / 32767.0f;
248 const float y = std::stof(seglist.at(1)) / 32767.0f;
249
250 return {x, y};
251}
252
253u32 Tas::ReadCommandButtons(const std::string& data) const {
254 std::stringstream button_text(data);
255 std::string line;
256 u32 buttons = 0;
257 while (std::getline(button_text, line, ';')) {
258 for (auto [text, tas_button] : text_to_tas_button) {
259 if (text == line) {
260 buttons |= static_cast<u32>(tas_button);
261 break;
262 }
263 }
264 }
265 return buttons;
266}
267
268std::string Tas::WriteCommandAxis(TasAnalog data) const {
269 auto [x, y] = data;
270 std::string line;
271 line += std::to_string(static_cast<int>(x * 32767));
272 line += ";";
273 line += std::to_string(static_cast<int>(y * 32767));
274 return line;
275}
276
277std::string Tas::WriteCommandButtons(u32 data) const {
278 if (data == 0) {
279 return "NONE";
280 }
281
282 std::string line;
283 u32 index = 0;
284 while (data > 0) {
285 if ((data & 1) == 1) {
286 for (auto [text, tas_button] : text_to_tas_button) {
287 if (tas_button == static_cast<TasButton>(1 << index)) {
288 if (line.size() > 0) {
289 line += ";";
290 }
291 line += text;
292 break;
293 }
294 }
295 }
296 index++;
297 data >>= 1;
298 }
299 return line;
300}
301
302void Tas::StartStop() {
303 if (!Settings::values.tas_enable) {
304 return;
305 }
306 if (is_running) {
307 Stop();
308 } else {
309 is_running = true;
310 SwapToTasController();
311 }
312}
313
314void Tas::Stop() {
315 is_running = false;
316 SwapToStoredController();
317}
318
319void Tas::SwapToTasController() {
320 if (!Settings::values.tas_swap_controllers) {
321 return;
322 }
323 auto& players = Settings::values.players.GetValue();
324 for (std::size_t index = 0; index < players.size(); index++) {
325 auto& player = players[index];
326 player_mappings[index] = player;
327
328 // Only swap active controllers
329 if (!player.connected) {
330 continue;
331 }
332
333 Common::ParamPackage tas_param;
334 tas_param.Set("pad", static_cast<u8>(index));
335 auto button_mapping = GetButtonMappingForDevice(tas_param);
336 auto analog_mapping = GetAnalogMappingForDevice(tas_param);
337 auto& buttons = player.buttons;
338 auto& analogs = player.analogs;
339
340 for (std::size_t i = 0; i < buttons.size(); ++i) {
341 buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize();
342 }
343 for (std::size_t i = 0; i < analogs.size(); ++i) {
344 analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize();
345 }
346 }
347 is_old_input_saved = true;
348 Settings::values.is_device_reload_pending.store(true);
349}
350
351void Tas::SwapToStoredController() {
352 if (!is_old_input_saved) {
353 return;
354 }
355 auto& players = Settings::values.players.GetValue();
356 for (std::size_t index = 0; index < players.size(); index++) {
357 players[index] = player_mappings[index];
358 }
359 is_old_input_saved = false;
360 Settings::values.is_device_reload_pending.store(true);
361}
362
363void Tas::Reset() {
364 if (!Settings::values.tas_enable) {
365 return;
366 }
367 needs_reset = true;
368}
369
370bool Tas::Record() {
371 if (!Settings::values.tas_enable) {
372 return true;
373 }
374 is_recording = !is_recording;
375 return is_recording;
376}
377
378void Tas::SaveRecording(bool overwrite_file) {
379 if (is_recording) {
380 return;
381 }
382 if (record_commands.empty()) {
383 return;
384 }
385 WriteTasFile(u8"record.txt");
386 if (overwrite_file) {
387 WriteTasFile(u8"script0-1.txt");
388 }
389 needs_reset = true;
390 record_commands.clear();
391}
392
393InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
394 const Common::ParamPackage& params) const {
395 // This list is missing ZL/ZR since those are not considered buttons.
396 // We will add those afterwards
397 // This list also excludes any button that can't be really mapped
398 static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20>
399 switch_to_tas_button = {
400 std::pair{Settings::NativeButton::A, TasButton::BUTTON_A},
401 {Settings::NativeButton::B, TasButton::BUTTON_B},
402 {Settings::NativeButton::X, TasButton::BUTTON_X},
403 {Settings::NativeButton::Y, TasButton::BUTTON_Y},
404 {Settings::NativeButton::LStick, TasButton::STICK_L},
405 {Settings::NativeButton::RStick, TasButton::STICK_R},
406 {Settings::NativeButton::L, TasButton::TRIGGER_L},
407 {Settings::NativeButton::R, TasButton::TRIGGER_R},
408 {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS},
409 {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS},
410 {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT},
411 {Settings::NativeButton::DUp, TasButton::BUTTON_UP},
412 {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT},
413 {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN},
414 {Settings::NativeButton::SL, TasButton::BUTTON_SL},
415 {Settings::NativeButton::SR, TasButton::BUTTON_SR},
416 {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE},
417 {Settings::NativeButton::Home, TasButton::BUTTON_HOME},
418 {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL},
419 {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR},
420 };
421
422 InputCommon::ButtonMapping mapping{};
423 for (const auto& [switch_button, tas_button] : switch_to_tas_button) {
424 Common::ParamPackage button_params({{"engine", "tas"}});
425 button_params.Set("pad", params.Get("pad", 0));
426 button_params.Set("button", static_cast<int>(tas_button));
427 mapping.insert_or_assign(switch_button, std::move(button_params));
428 }
429
430 return mapping;
431}
432
433InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice(
434 const Common::ParamPackage& params) const {
435
436 InputCommon::AnalogMapping mapping = {};
437 Common::ParamPackage left_analog_params;
438 left_analog_params.Set("engine", "tas");
439 left_analog_params.Set("pad", params.Get("pad", 0));
440 left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX));
441 left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY));
442 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
443 Common::ParamPackage right_analog_params;
444 right_analog_params.Set("engine", "tas");
445 right_analog_params.Set("pad", params.Get("pad", 0));
446 right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX));
447 right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY));
448 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
449 return mapping;
450}
451
452const TasData& Tas::GetTasState(std::size_t pad) const {
453 return tas_data[pad];
454}
455} // namespace TasInput
diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h
new file mode 100644
index 000000000..3e2db8f00
--- /dev/null
+++ b/src/input_common/tas/tas_input.h
@@ -0,0 +1,237 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8
9#include "common/common_types.h"
10#include "common/settings_input.h"
11#include "core/frontend/input.h"
12#include "input_common/main.h"
13
14/*
15To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below
16Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt
17for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players).
18
19A script file has the same format as TAS-nx uses, so final files will look like this:
20
211 KEY_B 0;0 0;0
226 KEY_ZL 0;0 0;0
2341 KEY_ZL;KEY_Y 0;0 0;0
2443 KEY_X;KEY_A 32767;0 0;0
2544 KEY_A 32767;0 0;0
2645 KEY_A 32767;0 0;0
2746 KEY_A 32767;0 0;0
2847 KEY_A 32767;0 0;0
29
30After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey
31CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file
32has. Playback can be started or stopped using CTRL+F5.
33
34However, for playback to actually work, the correct input device has to be selected: In the Controls
35menu, select TAS from the device list for the controller that the script should be played on.
36
37Recording a new script file is really simple: Just make sure that the proper device (not TAS) is
38connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke
39again (CTRL+F7). The new script will be saved at the location previously selected, as the filename
40record.txt.
41
42For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller
43P1).
44*/
45
46namespace TasInput {
47
48constexpr size_t PLAYER_NUMBER = 8;
49
50using TasAnalog = std::pair<float, float>;
51
52enum class TasState {
53 Running,
54 Recording,
55 Stopped,
56};
57
58enum class TasButton : u32 {
59 BUTTON_A = 1U << 0,
60 BUTTON_B = 1U << 1,
61 BUTTON_X = 1U << 2,
62 BUTTON_Y = 1U << 3,
63 STICK_L = 1U << 4,
64 STICK_R = 1U << 5,
65 TRIGGER_L = 1U << 6,
66 TRIGGER_R = 1U << 7,
67 TRIGGER_ZL = 1U << 8,
68 TRIGGER_ZR = 1U << 9,
69 BUTTON_PLUS = 1U << 10,
70 BUTTON_MINUS = 1U << 11,
71 BUTTON_LEFT = 1U << 12,
72 BUTTON_UP = 1U << 13,
73 BUTTON_RIGHT = 1U << 14,
74 BUTTON_DOWN = 1U << 15,
75 BUTTON_SL = 1U << 16,
76 BUTTON_SR = 1U << 17,
77 BUTTON_HOME = 1U << 18,
78 BUTTON_CAPTURE = 1U << 19,
79};
80
81enum class TasAxes : u8 {
82 StickX,
83 StickY,
84 SubstickX,
85 SubstickY,
86 Undefined,
87};
88
89struct TasData {
90 u32 buttons{};
91 std::array<float, 4> axis{};
92};
93
94class Tas {
95public:
96 Tas();
97 ~Tas();
98
99 // Changes the input status that will be stored in each frame
100 void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes);
101
102 // Main loop that records or executes input
103 void UpdateThread();
104
105 // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles
106 void StartStop();
107
108 // Stop the TAS and reverts any controller profile
109 void Stop();
110
111 // Sets the flag to reload the file and start from the begining in the next update
112 void Reset();
113
114 /**
115 * Sets the flag to enable or disable recording of inputs
116 * @return Returns true if the current recording status is enabled
117 */
118 bool Record();
119
120 // Saves contents of record_commands on a file if overwrite is enabled player 1 will be
121 // overwritten with the recorded commands
122 void SaveRecording(bool overwrite_file);
123
124 /**
125 * Returns the current status values of TAS playback/recording
126 * @return Tuple of
127 * TasState indicating the current state out of Running, Recording or Stopped ;
128 * Current playback progress or amount of frames (so far) for Recording ;
129 * Total length of script file currently loaded or amount of frames (so far) for Recording
130 */
131 std::tuple<TasState, size_t, size_t> GetStatus() const;
132
133 // Retuns an array of the default button mappings
134 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
135
136 // Retuns an array of the default analog mappings
137 InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
138 [[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
139
140private:
141 struct TASCommand {
142 u32 buttons{};
143 TasAnalog l_axis{};
144 TasAnalog r_axis{};
145 };
146
147 // Loads TAS files from all players
148 void LoadTasFiles();
149
150 // Loads TAS file from the specified player
151 void LoadTasFile(size_t player_index);
152
153 // Writes a TAS file from the recorded commands
154 void WriteTasFile(std::u8string file_name);
155
156 /**
157 * Parses a string containing the axis values with the following format "x;y"
158 * X and Y have a range from -32767 to 32767
159 * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0
160 */
161 TasAnalog ReadCommandAxis(const std::string& line) const;
162
163 /**
164 * Parses a string containing the button values with the following format "a;b;c;d..."
165 * Each button is represented by it's text format specified in text_to_tas_button array
166 * @return Returns a u32 with each bit representing the status of a button
167 */
168 u32 ReadCommandButtons(const std::string& line) const;
169
170 /**
171 * Converts an u32 containing the button status into the text equivalent
172 * @return Returns a string with the name of the buttons to be written to the file
173 */
174 std::string WriteCommandButtons(u32 data) const;
175
176 /**
177 * Converts an TAS analog object containing the axis status into the text equivalent
178 * @return Returns a string with the value of the axis to be written to the file
179 */
180 std::string WriteCommandAxis(TasAnalog data) const;
181
182 // Inverts the Y axis polarity
183 std::pair<float, float> FlipAxisY(std::pair<float, float> old);
184
185 /**
186 * Converts an u32 containing the button status into the text equivalent
187 * @return Returns a string with the name of the buttons to be printed on console
188 */
189 std::string DebugButtons(u32 buttons) const;
190
191 /**
192 * Converts an TAS analog object containing the axis status into the text equivalent
193 * @return Returns a string with the value of the axis to be printed on console
194 */
195 std::string DebugJoystick(float x, float y) const;
196
197 /**
198 * Converts the given TAS status into the text equivalent
199 * @return Returns a string with the value of the TAS status to be printed on console
200 */
201 std::string DebugInput(const TasData& data) const;
202
203 /**
204 * Converts the given TAS status of multiple players into the text equivalent
205 * @return Returns a string with the value of the status of all TAS players to be printed on
206 * console
207 */
208 std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const;
209
210 /**
211 * Converts an u32 containing the button status into the text equivalent
212 * @return Returns a string with the name of the buttons
213 */
214 std::string ButtonsToString(u32 button) const;
215
216 // Stores current controller configuration and sets a TAS controller for every active controller
217 // to the current config
218 void SwapToTasController();
219
220 // Sets the stored controller configuration to the current config
221 void SwapToStoredController();
222
223 size_t script_length{0};
224 std::array<TasData, PLAYER_NUMBER> tas_data;
225 bool is_old_input_saved{false};
226 bool is_recording{false};
227 bool is_running{false};
228 bool needs_reset{false};
229 std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
230 std::vector<TASCommand> record_commands{};
231 size_t current_command{0};
232 TASCommand last_input{}; // only used for recording
233
234 // Old settings for swapping controllers
235 std::array<Settings::PlayerInput, 10> player_mappings;
236};
237} // namespace TasInput
diff --git a/src/input_common/tas/tas_poller.cpp b/src/input_common/tas/tas_poller.cpp
new file mode 100644
index 000000000..15810d6b0
--- /dev/null
+++ b/src/input_common/tas/tas_poller.cpp
@@ -0,0 +1,101 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <utility>
7
8#include "common/settings.h"
9#include "common/threadsafe_queue.h"
10#include "input_common/tas/tas_input.h"
11#include "input_common/tas/tas_poller.h"
12
13namespace InputCommon {
14
15class TasButton final : public Input::ButtonDevice {
16public:
17 explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_)
18 : button(button_), pad(pad_), tas_input(tas_input_) {}
19
20 bool GetStatus() const override {
21 return (tas_input->GetTasState(pad).buttons & button) != 0;
22 }
23
24private:
25 const u32 button;
26 const u32 pad;
27 const TasInput::Tas* tas_input;
28};
29
30TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_)
31 : tas_input(std::move(tas_input_)) {}
32
33std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) {
34 const auto button_id = params.Get("button", 0);
35 const auto pad = params.Get("pad", 0);
36
37 return std::make_unique<TasButton>(button_id, pad, tas_input.get());
38}
39
40class TasAnalog final : public Input::AnalogDevice {
41public:
42 explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_)
43 : pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {}
44
45 float GetAxis(u32 axis) const {
46 std::lock_guard lock{mutex};
47 return tas_input->GetTasState(pad).axis.at(axis);
48 }
49
50 std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const {
51 float x = GetAxis(analog_axis_x);
52 float y = GetAxis(analog_axis_y);
53
54 // Make sure the coordinates are in the unit circle,
55 // otherwise normalize it.
56 float r = x * x + y * y;
57 if (r > 1.0f) {
58 r = std::sqrt(r);
59 x /= r;
60 y /= r;
61 }
62
63 return {x, y};
64 }
65
66 std::tuple<float, float> GetStatus() const override {
67 return GetAnalog(axis_x, axis_y);
68 }
69
70 Input::AnalogProperties GetAnalogProperties() const override {
71 return {0.0f, 1.0f, 0.5f};
72 }
73
74private:
75 const u32 pad;
76 const u32 axis_x;
77 const u32 axis_y;
78 const TasInput::Tas* tas_input;
79 mutable std::mutex mutex;
80};
81
82/// An analog device factory that creates analog devices from GC Adapter
83TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_)
84 : tas_input(std::move(tas_input_)) {}
85
86/**
87 * Creates analog device from joystick axes
88 * @param params contains parameters for creating the device:
89 * - "port": the nth gcpad on the adapter
90 * - "axis_x": the index of the axis to be bind as x-axis
91 * - "axis_y": the index of the axis to be bind as y-axis
92 */
93std::unique_ptr<Input::AnalogDevice> TasAnalogFactory::Create(const Common::ParamPackage& params) {
94 const auto pad = static_cast<u32>(params.Get("pad", 0));
95 const auto axis_x = static_cast<u32>(params.Get("axis_x", 0));
96 const auto axis_y = static_cast<u32>(params.Get("axis_y", 1));
97
98 return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get());
99}
100
101} // namespace InputCommon
diff --git a/src/input_common/tas/tas_poller.h b/src/input_common/tas/tas_poller.h
new file mode 100644
index 000000000..09e426cef
--- /dev/null
+++ b/src/input_common/tas/tas_poller.h
@@ -0,0 +1,43 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/tas/tas_input.h"
10
11namespace InputCommon {
12
13/**
14 * A button device factory representing a tas bot. It receives tas events and forward them
15 * to all button devices it created.
16 */
17class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> {
18public:
19 explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_);
20
21 /**
22 * Creates a button device from a button press
23 * @param params contains parameters for creating the device:
24 * - "code": the code of the key to bind with the button
25 */
26 std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override;
27
28private:
29 std::shared_ptr<TasInput::Tas> tas_input;
30};
31
32/// An analog device factory that creates analog devices from tas
33class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
34public:
35 explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_);
36
37 std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override;
38
39private:
40 std::shared_ptr<TasInput::Tas> tas_input;
41};
42
43} // namespace InputCommon
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
index 9b0aec797..b9512aa2e 100644
--- a/src/input_common/udp/client.cpp
+++ b/src/input_common/udp/client.cpp
@@ -471,46 +471,42 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
471 std::function<void(u16, u16, u16, u16)> data_callback) { 471 std::function<void(u16, u16, u16, u16)> data_callback) {
472 472
473 std::thread([=, this] { 473 std::thread([=, this] {
474 constexpr u16 CALIBRATION_THRESHOLD = 100;
475
476 u16 min_x{UINT16_MAX};
477 u16 min_y{UINT16_MAX};
478 u16 max_x{};
479 u16 max_y{};
480
481 Status current_status{Status::Initialized}; 474 Status current_status{Status::Initialized};
482 SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {}, 475 SocketCallback callback{
483 [&](Response::PadData data) { 476 [](Response::Version) {}, [](Response::PortInfo) {},
484 if (current_status == Status::Initialized) { 477 [&](Response::PadData data) {
485 // Receiving data means the communication is ready now 478 static constexpr u16 CALIBRATION_THRESHOLD = 100;
486 current_status = Status::Ready; 479 static constexpr u16 MAX_VALUE = UINT16_MAX;
487 status_callback(current_status); 480
488 } 481 if (current_status == Status::Initialized) {
489 if (data.touch[0].is_active == 0) { 482 // Receiving data means the communication is ready now
490 return; 483 current_status = Status::Ready;
491 } 484 status_callback(current_status);
492 LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x, 485 }
493 data.touch[0].y); 486 const auto& touchpad_0 = data.touch[0];
494 min_x = std::min(min_x, static_cast<u16>(data.touch[0].x)); 487 if (touchpad_0.is_active == 0) {
495 min_y = std::min(min_y, static_cast<u16>(data.touch[0].y)); 488 return;
496 if (current_status == Status::Ready) { 489 }
497 // First touch - min data (min_x/min_y) 490 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
498 current_status = Status::Stage1Completed; 491 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
499 status_callback(current_status); 492 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
500 } 493 if (current_status == Status::Ready) {
501 if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD && 494 // First touch - min data (min_x/min_y)
502 data.touch[0].y - min_y > CALIBRATION_THRESHOLD) { 495 current_status = Status::Stage1Completed;
503 // Set the current position as max value and finishes 496 status_callback(current_status);
504 // configuration 497 }
505 max_x = data.touch[0].x; 498 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
506 max_y = data.touch[0].y; 499 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
507 current_status = Status::Completed; 500 // Set the current position as max value and finishes configuration
508 data_callback(min_x, min_y, max_x, max_y); 501 const u16 max_x = touchpad_0.x;
509 status_callback(current_status); 502 const u16 max_y = touchpad_0.y;
510 503 current_status = Status::Completed;
511 complete_event.Set(); 504 data_callback(min_x, min_y, max_x, max_y);
512 } 505 status_callback(current_status);
513 }}; 506
507 complete_event.Set();
508 }
509 }};
514 Socket socket{host, port, std::move(callback)}; 510 Socket socket{host, port, std::move(callback)};
515 std::thread worker_thread{SocketLoop, &socket}; 511 std::thread worker_thread{SocketLoop, &socket};
516 complete_event.Wait(); 512 complete_event.Wait();
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index a11ea3068..380f9bb76 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -21,8 +21,6 @@
21 21
22namespace InputCommon::CemuhookUDP { 22namespace InputCommon::CemuhookUDP {
23 23
24constexpr char DEFAULT_SRV[] = "127.0.0.1:26760";
25
26class Socket; 24class Socket;
27 25
28namespace Response { 26namespace Response {