summaryrefslogtreecommitdiff
path: root/src/input_common
diff options
context:
space:
mode:
authorGravatar MonsterDruide12021-06-18 16:15:42 +0200
committerGravatar MonsterDruide12021-09-18 23:22:00 +0200
commitb42c3ce21db249d5e3bc04b4f73202e757da317c (patch)
treec5ccf9e311d2a675b0e2afff3d85ed4654047b5e /src/input_common
parentMerge pull request #7020 from Moonlacer/remove_audio_stretching (diff)
downloadyuzu-b42c3ce21db249d5e3bc04b4f73202e757da317c.tar.gz
yuzu-b42c3ce21db249d5e3bc04b4f73202e757da317c.tar.xz
yuzu-b42c3ce21db249d5e3bc04b4f73202e757da317c.zip
input_common/tas: Base playback & recording system
The base playback system supports up to 8 controllers (specified by `PLAYER_NUMBER` in `tas_input.h`), which all change their inputs simulataneously when `TAS::UpdateThread` is called. The recording system uses the controller debugger to read the state of the first controller and forwards that data to the TASing system for recording. Currently, this process sadly is not frame-perfect and pixel-accurate. Co-authored-by: Naii-the-Baf <sfabian200@gmail.com> Co-authored-by: Narr-the-Reg <juangerman-13@hotmail.com>
Diffstat (limited to 'src/input_common')
-rw-r--r--src/input_common/CMakeLists.txt4
-rw-r--r--src/input_common/main.cpp48
-rw-r--r--src/input_common/main.h23
-rw-r--r--src/input_common/tas/tas_input.cpp340
-rw-r--r--src/input_common/tas/tas_input.h163
-rw-r--r--src/input_common/tas/tas_poller.cpp101
-rw-r--r--src/input_common/tas/tas_poller.h43
7 files changed, 722 insertions, 0 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 ff23230f0..4f170493e 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -13,6 +13,8 @@
13#include "input_common/motion_from_button.h" 13#include "input_common/motion_from_button.h"
14#include "input_common/mouse/mouse_input.h" 14#include "input_common/mouse/mouse_input.h"
15#include "input_common/mouse/mouse_poller.h" 15#include "input_common/mouse/mouse_poller.h"
16#include "input_common/tas/tas_input.h"
17#include "input_common/tas/tas_poller.h"
16#include "input_common/touch_from_button.h" 18#include "input_common/touch_from_button.h"
17#include "input_common/udp/client.h" 19#include "input_common/udp/client.h"
18#include "input_common/udp/udp.h" 20#include "input_common/udp/udp.h"
@@ -60,6 +62,12 @@ struct InputSubsystem::Impl {
60 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion); 62 Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion);
61 mousetouch = std::make_shared<MouseTouchFactory>(mouse); 63 mousetouch = std::make_shared<MouseTouchFactory>(mouse);
62 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch); 64 Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch);
65
66 tas = std::make_shared<TasInput::Tas>();
67 tasbuttons = std::make_shared<TasButtonFactory>(tas);
68 Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons);
69 tasanalog = std::make_shared<TasAnalogFactory>(tas);
70 Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog);
63 } 71 }
64 72
65 void Shutdown() { 73 void Shutdown() {
@@ -94,12 +102,19 @@ struct InputSubsystem::Impl {
94 mouseanalog.reset(); 102 mouseanalog.reset();
95 mousemotion.reset(); 103 mousemotion.reset();
96 mousetouch.reset(); 104 mousetouch.reset();
105
106 Input::UnregisterFactory<Input::ButtonDevice>("tas");
107 Input::UnregisterFactory<Input::AnalogDevice>("tas");
108
109 tasbuttons.reset();
110 tasanalog.reset();
97 } 111 }
98 112
99 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { 113 [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const {
100 std::vector<Common::ParamPackage> devices = { 114 std::vector<Common::ParamPackage> devices = {
101 Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, 115 Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
102 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, 116 Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
117 Common::ParamPackage{{"display", "TAS"}, {"class", "tas"}},
103 }; 118 };
104#ifdef HAVE_SDL2 119#ifdef HAVE_SDL2
105 auto sdl_devices = sdl->GetInputDevices(); 120 auto sdl_devices = sdl->GetInputDevices();
@@ -120,6 +135,9 @@ struct InputSubsystem::Impl {
120 if (params.Get("class", "") == "gcpad") { 135 if (params.Get("class", "") == "gcpad") {
121 return gcadapter->GetAnalogMappingForDevice(params); 136 return gcadapter->GetAnalogMappingForDevice(params);
122 } 137 }
138 if (params.Get("class", "") == "tas") {
139 return tas->GetAnalogMappingForDevice(params);
140 }
123#ifdef HAVE_SDL2 141#ifdef HAVE_SDL2
124 if (params.Get("class", "") == "sdl") { 142 if (params.Get("class", "") == "sdl") {
125 return sdl->GetAnalogMappingForDevice(params); 143 return sdl->GetAnalogMappingForDevice(params);
@@ -136,6 +154,9 @@ struct InputSubsystem::Impl {
136 if (params.Get("class", "") == "gcpad") { 154 if (params.Get("class", "") == "gcpad") {
137 return gcadapter->GetButtonMappingForDevice(params); 155 return gcadapter->GetButtonMappingForDevice(params);
138 } 156 }
157 if (params.Get("class", "") == "tas") {
158 return tas->GetButtonMappingForDevice(params);
159 }
139#ifdef HAVE_SDL2 160#ifdef HAVE_SDL2
140 if (params.Get("class", "") == "sdl") { 161 if (params.Get("class", "") == "sdl") {
141 return sdl->GetButtonMappingForDevice(params); 162 return sdl->GetButtonMappingForDevice(params);
@@ -174,9 +195,12 @@ struct InputSubsystem::Impl {
174 std::shared_ptr<MouseAnalogFactory> mouseanalog; 195 std::shared_ptr<MouseAnalogFactory> mouseanalog;
175 std::shared_ptr<MouseMotionFactory> mousemotion; 196 std::shared_ptr<MouseMotionFactory> mousemotion;
176 std::shared_ptr<MouseTouchFactory> mousetouch; 197 std::shared_ptr<MouseTouchFactory> mousetouch;
198 std::shared_ptr<TasButtonFactory> tasbuttons;
199 std::shared_ptr<TasAnalogFactory> tasanalog;
177 std::shared_ptr<CemuhookUDP::Client> udp; 200 std::shared_ptr<CemuhookUDP::Client> udp;
178 std::shared_ptr<GCAdapter::Adapter> gcadapter; 201 std::shared_ptr<GCAdapter::Adapter> gcadapter;
179 std::shared_ptr<MouseInput::Mouse> mouse; 202 std::shared_ptr<MouseInput::Mouse> mouse;
203 std::shared_ptr<TasInput::Tas> tas;
180}; 204};
181 205
182InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} 206InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {}
@@ -207,6 +231,14 @@ const MouseInput::Mouse* InputSubsystem::GetMouse() const {
207 return impl->mouse.get(); 231 return impl->mouse.get();
208} 232}
209 233
234TasInput::Tas* InputSubsystem::GetTas() {
235 return impl->tas.get();
236}
237
238const TasInput::Tas* InputSubsystem::GetTas() const {
239 return impl->tas.get();
240}
241
210std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { 242std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
211 return impl->GetInputDevices(); 243 return impl->GetInputDevices();
212} 244}
@@ -287,6 +319,22 @@ const MouseTouchFactory* InputSubsystem::GetMouseTouch() const {
287 return impl->mousetouch.get(); 319 return impl->mousetouch.get();
288} 320}
289 321
322TasButtonFactory* InputSubsystem::GetTasButtons() {
323 return impl->tasbuttons.get();
324}
325
326const TasButtonFactory* InputSubsystem::GetTasButtons() const {
327 return impl->tasbuttons.get();
328}
329
330TasAnalogFactory* InputSubsystem::GetTasAnalogs() {
331 return impl->tasanalog.get();
332}
333
334const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const {
335 return impl->tasanalog.get();
336}
337
290void InputSubsystem::ReloadInputDevices() { 338void InputSubsystem::ReloadInputDevices() {
291 if (!impl->udp) { 339 if (!impl->udp) {
292 return; 340 return;
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 5d6f26385..1d06fc5f5 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
@@ -168,6 +179,18 @@ public:
168 /// Retrieves the underlying udp touch handler. 179 /// Retrieves the underlying udp 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 touch handler.
189 [[nodiscard]] TasAnalogFactory* GetTasAnalogs();
190
191 /// Retrieves the underlying tas touch 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/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp
new file mode 100644
index 000000000..343641945
--- /dev/null
+++ b/src/input_common/tas/tas_input.cpp
@@ -0,0 +1,340 @@
1// Copyright 2021 yuzu Emulator Project
2// Licensed under GPLv2+
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cstring>
7#include <functional>
8#include <random>
9#include <regex>
10#include <thread>
11#include <boost/asio.hpp>
12
13#include "common/fs/file.h"
14#include "common/fs/fs_types.h"
15#include "common/fs/path_util.h"
16#include "common/logging/log.h"
17#include "common/settings.h"
18#include "input_common/tas/tas_input.h"
19
20namespace TasInput {
21
22Tas::Tas() {
23 LoadTasFiles();
24}
25
26Tas::~Tas() {
27 update_thread_running = false;
28}
29
30void Tas::RefreshTasFile() {
31 refresh_tas_fle = true;
32}
33void Tas::LoadTasFiles() {
34 scriptLength = 0;
35 for (int i = 0; i < PLAYER_NUMBER; i++) {
36 LoadTasFile(i);
37 if (newCommands[i].size() > scriptLength)
38 scriptLength = newCommands[i].size();
39 }
40}
41void Tas::LoadTasFile(int playerIndex) {
42 LOG_DEBUG(Input, "LoadTasFile()");
43 if (!newCommands[playerIndex].empty()) {
44 newCommands[playerIndex].clear();
45 }
46 std::string file = Common::FS::ReadStringFromFile(
47 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" +
48 std::to_string(playerIndex + 1) + ".txt",
49 Common::FS::FileType::BinaryFile);
50 std::stringstream command_line(file);
51 std::string line;
52 int frameNo = 0;
53 TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}};
54 while (std::getline(command_line, line, '\n')) {
55 if (line.empty())
56 continue;
57 LOG_DEBUG(Input, "Loading line: {}", line);
58 std::smatch m;
59
60 std::stringstream linestream(line);
61 std::string segment;
62 std::vector<std::string> seglist;
63
64 while (std::getline(linestream, segment, ' ')) {
65 seglist.push_back(segment);
66 }
67
68 if (seglist.size() < 4)
69 continue;
70
71 while (frameNo < std::stoi(seglist.at(0))) {
72 newCommands[playerIndex].push_back(empty);
73 frameNo++;
74 }
75
76 TASCommand command = {
77 .buttons = ReadCommandButtons(seglist.at(1)),
78 .l_axis = ReadCommandAxis(seglist.at(2)),
79 .r_axis = ReadCommandAxis(seglist.at(3)),
80 };
81 newCommands[playerIndex].push_back(command);
82 frameNo++;
83 }
84 LOG_INFO(Input, "TAS file loaded! {} frames", frameNo);
85}
86
87void Tas::WriteTasFile() {
88 LOG_DEBUG(Input, "WriteTasFile()");
89 std::string output_text = "";
90 for (int frame = 0; frame < (signed)recordCommands.size(); frame++) {
91 if (!output_text.empty())
92 output_text += "\n";
93 TASCommand line = recordCommands.at(frame);
94 output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
95 WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
96 }
97 size_t bytesWritten = Common::FS::WriteStringToFile(
98 Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt",
99 Common::FS::FileType::TextFile, output_text);
100 if (bytesWritten == output_text.size())
101 LOG_INFO(Input, "TAS file written to file!");
102 else
103 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten,
104 output_text.size());
105}
106
107void Tas::RecordInput(u32 buttons, std::array<std::pair<float, float>, 2> axes) {
108 lastInput = {buttons, flipY(axes[0]), flipY(axes[1])};
109}
110
111std::pair<float, float> Tas::flipY(std::pair<float, float> old) const {
112 auto [x, y] = old;
113 return {x, -y};
114}
115
116std::string Tas::GetStatusDescription() {
117 if (Settings::values.tas_record) {
118 return "Recording TAS: " + std::to_string(recordCommands.size());
119 }
120 if (Settings::values.tas_enable) {
121 return "Playing TAS: " + std::to_string(current_command) + "/" +
122 std::to_string(scriptLength);
123 }
124 return "TAS not running: " + std::to_string(current_command) + "/" +
125 std::to_string(scriptLength);
126}
127
128std::string debugButtons(u32 buttons) {
129 return "{ " + TasInput::Tas::buttonsToString(buttons) + " }";
130}
131
132std::string debugJoystick(float x, float y) {
133 return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]";
134}
135
136std::string debugInput(TasData data) {
137 return "{ " + debugButtons(data.buttons) + " , " + debugJoystick(data.axis[0], data.axis[1]) +
138 " , " + debugJoystick(data.axis[2], data.axis[3]) + " }";
139}
140
141std::string debugInputs(std::array<TasData, PLAYER_NUMBER> arr) {
142 std::string returns = "[ ";
143 for (size_t i = 0; i < arr.size(); i++) {
144 returns += debugInput(arr[i]);
145 if (i != arr.size() - 1)
146 returns += " , ";
147 }
148 return returns + "]";
149}
150
151void Tas::UpdateThread() {
152 if (update_thread_running) {
153 if (Settings::values.pauseTasOnLoad && Settings::values.cpuBoosted) {
154 for (int i = 0; i < PLAYER_NUMBER; i++) {
155 tas_data[i].buttons = 0;
156 tas_data[i].axis = {};
157 }
158 }
159
160 if (Settings::values.tas_record) {
161 recordCommands.push_back(lastInput);
162 }
163 if (!Settings::values.tas_record && !recordCommands.empty()) {
164 WriteTasFile();
165 Settings::values.tas_reset = true;
166 refresh_tas_fle = true;
167 recordCommands.clear();
168 }
169 if (Settings::values.tas_reset) {
170 current_command = 0;
171 if (refresh_tas_fle) {
172 LoadTasFiles();
173 refresh_tas_fle = false;
174 }
175 Settings::values.tas_reset = false;
176 LoadTasFiles();
177 LOG_DEBUG(Input, "tas_reset done");
178 }
179 if (Settings::values.tas_enable) {
180 if ((signed)current_command < scriptLength) {
181 LOG_INFO(Input, "Playing TAS {}/{}", current_command, scriptLength);
182 size_t frame = current_command++;
183 for (int i = 0; i < PLAYER_NUMBER; i++) {
184 if (frame < newCommands[i].size()) {
185 TASCommand command = newCommands[i][frame];
186 tas_data[i].buttons = command.buttons;
187 auto [l_axis_x, l_axis_y] = command.l_axis;
188 tas_data[i].axis[0] = l_axis_x;
189 tas_data[i].axis[1] = l_axis_y;
190 auto [r_axis_x, r_axis_y] = command.r_axis;
191 tas_data[i].axis[2] = r_axis_x;
192 tas_data[i].axis[3] = r_axis_y;
193 } else {
194 tas_data[i].buttons = 0;
195 tas_data[i].axis = {};
196 }
197 }
198 } else {
199 Settings::values.tas_enable = false;
200 current_command = 0;
201 for (int i = 0; i < PLAYER_NUMBER; i++) {
202 tas_data[i].buttons = 0;
203 tas_data[i].axis = {};
204 }
205 }
206 } else {
207 for (int i = 0; i < PLAYER_NUMBER; i++) {
208 tas_data[i].buttons = 0;
209 tas_data[i].axis = {};
210 }
211 }
212 }
213 LOG_DEBUG(Input, "TAS inputs: {}", debugInputs(tas_data));
214}
215
216TasAnalog Tas::ReadCommandAxis(const std::string line) const {
217 std::stringstream linestream(line);
218 std::string segment;
219 std::vector<std::string> seglist;
220
221 while (std::getline(linestream, segment, ';')) {
222 seglist.push_back(segment);
223 }
224
225 const float x = std::stof(seglist.at(0)) / 32767.f;
226 const float y = std::stof(seglist.at(1)) / 32767.f;
227
228 return {x, y};
229}
230
231u32 Tas::ReadCommandButtons(const std::string data) const {
232 std::stringstream button_text(data);
233 std::string line;
234 u32 buttons = 0;
235 while (std::getline(button_text, line, ';')) {
236 for (auto [text, tas_button] : text_to_tas_button) {
237 if (text == line) {
238 buttons |= static_cast<u32>(tas_button);
239 break;
240 }
241 }
242 }
243 return buttons;
244}
245
246std::string Tas::WriteCommandAxis(TasAnalog data) const {
247 auto [x, y] = data;
248 std::string line;
249 line += std::to_string(static_cast<int>(x * 32767));
250 line += ";";
251 line += std::to_string(static_cast<int>(y * 32767));
252 return line;
253}
254
255std::string Tas::WriteCommandButtons(u32 data) const {
256 if (data == 0)
257 return "NONE";
258
259 std::string line;
260 u32 index = 0;
261 while (data > 0) {
262 if ((data & 1) == 1) {
263 for (auto [text, tas_button] : text_to_tas_button) {
264 if (tas_button == static_cast<TasButton>(1 << index)) {
265 if (line.size() > 0)
266 line += ";";
267 line += text;
268 break;
269 }
270 }
271 }
272 index++;
273 data >>= 1;
274 }
275 return line;
276}
277
278InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
279 const Common::ParamPackage& params) const {
280 // This list is missing ZL/ZR since those are not considered buttons.
281 // We will add those afterwards
282 // This list also excludes any button that can't be really mapped
283 static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20>
284 switch_to_tas_button = {
285 std::pair{Settings::NativeButton::A, TasButton::BUTTON_A},
286 {Settings::NativeButton::B, TasButton::BUTTON_B},
287 {Settings::NativeButton::X, TasButton::BUTTON_X},
288 {Settings::NativeButton::Y, TasButton::BUTTON_Y},
289 {Settings::NativeButton::LStick, TasButton::STICK_L},
290 {Settings::NativeButton::RStick, TasButton::STICK_R},
291 {Settings::NativeButton::L, TasButton::TRIGGER_L},
292 {Settings::NativeButton::R, TasButton::TRIGGER_R},
293 {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS},
294 {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS},
295 {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT},
296 {Settings::NativeButton::DUp, TasButton::BUTTON_UP},
297 {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT},
298 {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN},
299 {Settings::NativeButton::SL, TasButton::BUTTON_SL},
300 {Settings::NativeButton::SR, TasButton::BUTTON_SR},
301 {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE},
302 {Settings::NativeButton::Home, TasButton::BUTTON_HOME},
303 {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL},
304 {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR},
305 };
306
307 InputCommon::ButtonMapping mapping{};
308 for (const auto& [switch_button, tas_button] : switch_to_tas_button) {
309 Common::ParamPackage button_params({{"engine", "tas"}});
310 button_params.Set("pad", params.Get("pad", 0));
311 button_params.Set("button", static_cast<int>(tas_button));
312 mapping.insert_or_assign(switch_button, std::move(button_params));
313 }
314
315 return mapping;
316}
317
318InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice(
319 const Common::ParamPackage& params) const {
320
321 InputCommon::AnalogMapping mapping = {};
322 Common::ParamPackage left_analog_params;
323 left_analog_params.Set("engine", "tas");
324 left_analog_params.Set("pad", params.Get("pad", 0));
325 left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX));
326 left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY));
327 mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
328 Common::ParamPackage right_analog_params;
329 right_analog_params.Set("engine", "tas");
330 right_analog_params.Set("pad", params.Get("pad", 0));
331 right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX));
332 right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY));
333 mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
334 return mapping;
335}
336
337const TasData& Tas::GetTasState(std::size_t pad) const {
338 return tas_data[pad];
339}
340} // 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..0a152a04f
--- /dev/null
+++ b/src/input_common/tas/tas_input.h
@@ -0,0 +1,163 @@
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#include <mutex>
9#include <thread>
10
11#include "common/common_types.h"
12#include "core/frontend/input.h"
13#include "input_common/main.h"
14
15#define PLAYER_NUMBER 8
16
17namespace TasInput {
18
19using TasAnalog = std::tuple<float, float>;
20
21enum class TasButton : u32 {
22 BUTTON_A = 0x000001,
23 BUTTON_B = 0x000002,
24 BUTTON_X = 0x000004,
25 BUTTON_Y = 0x000008,
26 STICK_L = 0x000010,
27 STICK_R = 0x000020,
28 TRIGGER_L = 0x000040,
29 TRIGGER_R = 0x000080,
30 TRIGGER_ZL = 0x000100,
31 TRIGGER_ZR = 0x000200,
32 BUTTON_PLUS = 0x000400,
33 BUTTON_MINUS = 0x000800,
34 BUTTON_LEFT = 0x001000,
35 BUTTON_UP = 0x002000,
36 BUTTON_RIGHT = 0x004000,
37 BUTTON_DOWN = 0x008000,
38 BUTTON_SL = 0x010000,
39 BUTTON_SR = 0x020000,
40 BUTTON_HOME = 0x040000,
41 BUTTON_CAPTURE = 0x080000,
42};
43
44static const std::array<std::pair<std::string, TasButton>, 20> text_to_tas_button = {
45 std::pair{"KEY_A", TasButton::BUTTON_A},
46 {"KEY_B", TasButton::BUTTON_B},
47 {"KEY_X", TasButton::BUTTON_X},
48 {"KEY_Y", TasButton::BUTTON_Y},
49 {"KEY_LSTICK", TasButton::STICK_L},
50 {"KEY_RSTICK", TasButton::STICK_R},
51 {"KEY_L", TasButton::TRIGGER_L},
52 {"KEY_R", TasButton::TRIGGER_R},
53 {"KEY_PLUS", TasButton::BUTTON_PLUS},
54 {"KEY_MINUS", TasButton::BUTTON_MINUS},
55 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
56 {"KEY_DUP", TasButton::BUTTON_UP},
57 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
58 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
59 {"KEY_SL", TasButton::BUTTON_SL},
60 {"KEY_SR", TasButton::BUTTON_SR},
61 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
62 {"KEY_HOME", TasButton::BUTTON_HOME},
63 {"KEY_ZL", TasButton::TRIGGER_ZL},
64 {"KEY_ZR", TasButton::TRIGGER_ZR},
65};
66
67enum class TasAxes : u8 {
68 StickX,
69 StickY,
70 SubstickX,
71 SubstickY,
72 Undefined,
73};
74
75struct TasData {
76 u32 buttons{};
77 std::array<float, 4> axis{};
78};
79
80class Tas {
81public:
82 Tas();
83 ~Tas();
84
85 static std::string buttonsToString(u32 button) {
86 std::string returns;
87 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_A)) != 0)
88 returns += ", A";
89 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_B)) != 0)
90 returns += ", B";
91 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_X)) != 0)
92 returns += ", X";
93 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_Y)) != 0)
94 returns += ", Y";
95 if ((button & static_cast<u32>(TasInput::TasButton::STICK_L)) != 0)
96 returns += ", STICK_L";
97 if ((button & static_cast<u32>(TasInput::TasButton::STICK_R)) != 0)
98 returns += ", STICK_R";
99 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_L)) != 0)
100 returns += ", TRIGGER_L";
101 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_R)) != 0)
102 returns += ", TRIGGER_R";
103 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_ZL)) != 0)
104 returns += ", TRIGGER_ZL";
105 if ((button & static_cast<u32>(TasInput::TasButton::TRIGGER_ZR)) != 0)
106 returns += ", TRIGGER_ZR";
107 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_PLUS)) != 0)
108 returns += ", PLUS";
109 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_MINUS)) != 0)
110 returns += ", MINUS";
111 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_LEFT)) != 0)
112 returns += ", LEFT";
113 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_UP)) != 0)
114 returns += ", UP";
115 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_RIGHT)) != 0)
116 returns += ", RIGHT";
117 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_DOWN)) != 0)
118 returns += ", DOWN";
119 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_SL)) != 0)
120 returns += ", SL";
121 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_SR)) != 0)
122 returns += ", SR";
123 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_HOME)) != 0)
124 returns += ", HOME";
125 if ((button & static_cast<u32>(TasInput::TasButton::BUTTON_CAPTURE)) != 0)
126 returns += ", CAPTURE";
127 return returns.length() != 0 ? returns.substr(2) : "";
128 }
129
130 void RefreshTasFile();
131 void LoadTasFiles();
132 void RecordInput(u32 buttons, std::array<std::pair<float, float>, 2> axes);
133 void UpdateThread();
134 std::string GetStatusDescription();
135
136 InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
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 void LoadTasFile(int playerIndex);
147 void WriteTasFile();
148 TasAnalog ReadCommandAxis(const std::string line) const;
149 u32 ReadCommandButtons(const std::string line) const;
150 std::string WriteCommandButtons(u32 data) const;
151 std::string WriteCommandAxis(TasAnalog data) const;
152 std::pair<float, float> flipY(std::pair<float, float> old) const;
153
154 size_t scriptLength{0};
155 std::array<TasData, PLAYER_NUMBER> tas_data;
156 bool update_thread_running{true};
157 bool refresh_tas_fle{false};
158 std::array<std::vector<TASCommand>, PLAYER_NUMBER> newCommands{};
159 std::vector<TASCommand> recordCommands{};
160 std::size_t current_command{0};
161 TASCommand lastInput{}; // only used for recording
162};
163} // 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..1bc0d173b
--- /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 mouse. It receives mouse 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 mouse
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