summaryrefslogtreecommitdiff
path: root/src/input_common/drivers/tas_input.cpp
diff options
context:
space:
mode:
authorGravatar german772021-09-20 17:22:07 -0500
committerGravatar Narr the Reg2021-11-24 20:30:22 -0600
commit395e9a449d338e56ade9ea88ffeed297a7d86b10 (patch)
tree74b7257d84bbb368d180fd230293501535509da1 /src/input_common/drivers/tas_input.cpp
parentinput_common: Rewrite touch (diff)
downloadyuzu-395e9a449d338e56ade9ea88ffeed297a7d86b10.tar.gz
yuzu-395e9a449d338e56ade9ea88ffeed297a7d86b10.tar.xz
yuzu-395e9a449d338e56ade9ea88ffeed297a7d86b10.zip
input_common: Rewrite gc_adapter
Diffstat (limited to 'src/input_common/drivers/tas_input.cpp')
-rw-r--r--src/input_common/drivers/tas_input.cpp320
1 files changed, 320 insertions, 0 deletions
diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp
new file mode 100644
index 000000000..5e2101b27
--- /dev/null
+++ b/src/input_common/drivers/tas_input.cpp
@@ -0,0 +1,320 @@
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#include <fmt/format.h>
8
9#include "common/fs/file.h"
10#include "common/fs/fs_types.h"
11#include "common/fs/path_util.h"
12#include "common/logging/log.h"
13#include "common/settings.h"
14#include "input_common/drivers/tas_input.h"
15
16namespace InputCommon::TasInput {
17
18enum TasAxes : u8 {
19 StickX,
20 StickY,
21 SubstickX,
22 SubstickY,
23 Undefined,
24};
25
26// Supported keywords and buttons from a TAS file
27constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
28 std::pair{"KEY_A", TasButton::BUTTON_A},
29 {"KEY_B", TasButton::BUTTON_B},
30 {"KEY_X", TasButton::BUTTON_X},
31 {"KEY_Y", TasButton::BUTTON_Y},
32 {"KEY_LSTICK", TasButton::STICK_L},
33 {"KEY_RSTICK", TasButton::STICK_R},
34 {"KEY_L", TasButton::TRIGGER_L},
35 {"KEY_R", TasButton::TRIGGER_R},
36 {"KEY_PLUS", TasButton::BUTTON_PLUS},
37 {"KEY_MINUS", TasButton::BUTTON_MINUS},
38 {"KEY_DLEFT", TasButton::BUTTON_LEFT},
39 {"KEY_DUP", TasButton::BUTTON_UP},
40 {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
41 {"KEY_DDOWN", TasButton::BUTTON_DOWN},
42 {"KEY_SL", TasButton::BUTTON_SL},
43 {"KEY_SR", TasButton::BUTTON_SR},
44 {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
45 {"KEY_HOME", TasButton::BUTTON_HOME},
46 {"KEY_ZL", TasButton::TRIGGER_ZL},
47 {"KEY_ZR", TasButton::TRIGGER_ZR},
48};
49
50Tas::Tas(const std::string input_engine_) : InputCommon::InputEngine(input_engine_) {
51 for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
52 PadIdentifier identifier{
53 .guid = Common::UUID{},
54 .port = player_index,
55 .pad = 0,
56 };
57 PreSetController(identifier);
58 }
59 ClearInput();
60 if (!Settings::values.tas_enable) {
61 needs_reset = true;
62 return;
63 }
64 LoadTasFiles();
65}
66
67Tas::~Tas() {
68 Stop();
69};
70
71void Tas::LoadTasFiles() {
72 script_length = 0;
73 for (size_t i = 0; i < commands.size(); i++) {
74 LoadTasFile(i);
75 if (commands[i].size() > script_length) {
76 script_length = commands[i].size();
77 }
78 }
79}
80
81void Tas::LoadTasFile(size_t player_index) {
82 if (!commands[player_index].empty()) {
83 commands[player_index].clear();
84 }
85 std::string file =
86 Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
87 fmt::format("script0-{}.txt", player_index + 1),
88 Common::FS::FileType::BinaryFile);
89 std::stringstream command_line(file);
90 std::string line;
91 int frame_no = 0;
92 while (std::getline(command_line, line, '\n')) {
93 if (line.empty()) {
94 continue;
95 }
96 std::smatch m;
97
98 std::stringstream linestream(line);
99 std::string segment;
100 std::vector<std::string> seglist;
101
102 while (std::getline(linestream, segment, ' ')) {
103 seglist.push_back(segment);
104 }
105
106 if (seglist.size() < 4) {
107 continue;
108 }
109
110 while (frame_no < std::stoi(seglist.at(0))) {
111 commands[player_index].push_back({});
112 frame_no++;
113 }
114
115 TASCommand command = {
116 .buttons = ReadCommandButtons(seglist.at(1)),
117 .l_axis = ReadCommandAxis(seglist.at(2)),
118 .r_axis = ReadCommandAxis(seglist.at(3)),
119 };
120 commands[player_index].push_back(command);
121 frame_no++;
122 }
123 LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
124}
125
126void Tas::WriteTasFile(std::u8string file_name) {
127 std::string output_text;
128 for (size_t frame = 0; frame < record_commands.size(); frame++) {
129 const TASCommand& line = record_commands[frame];
130 output_text += fmt::format("{} {} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
131 WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
132 }
133 const auto bytes_written = Common::FS::WriteStringToFile(
134 Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name,
135 Common::FS::FileType::TextFile, output_text);
136 if (bytes_written == output_text.size()) {
137 LOG_INFO(Input, "TAS file written to file!");
138 } else {
139 LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
140 output_text.size());
141 }
142}
143
144void Tas::RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis) {
145 last_input = {
146 .buttons = buttons,
147 .l_axis = FlipAxisY(left_axis),
148 .r_axis = FlipAxisY(right_axis),
149 };
150}
151
152TasAnalog Tas::FlipAxisY(TasAnalog old) {
153 return {
154 .x = old.x,
155 .y = -old.y,
156 };
157}
158
159std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
160 TasState state;
161 if (is_recording) {
162 return {TasState::Recording, 0, record_commands.size()};
163 }
164
165 if (is_running) {
166 state = TasState::Running;
167 } else {
168 state = TasState::Stopped;
169 }
170
171 return {state, current_command, script_length};
172}
173
174void Tas::UpdateThread() {
175 if (!Settings::values.tas_enable) {
176 if (is_running) {
177 Stop();
178 }
179 return;
180 }
181
182 if (is_recording) {
183 record_commands.push_back(last_input);
184 }
185 if (needs_reset) {
186 current_command = 0;
187 needs_reset = false;
188 LoadTasFiles();
189 LOG_DEBUG(Input, "tas_reset done");
190 }
191
192 if (!is_running) {
193 ClearInput();
194 return;
195 }
196 if (current_command < script_length) {
197 LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
198 size_t frame = current_command++;
199 for (size_t player_index = 0; player_index < commands.size(); player_index++) {
200 TASCommand command{};
201 if (frame < commands[player_index].size()) {
202 command = commands[player_index][frame];
203 }
204
205 PadIdentifier identifier{
206 .guid = Common::UUID{},
207 .port = player_index,
208 .pad = 0,
209 };
210 for (std::size_t i = 0; i < sizeof(command.buttons); ++i) {
211 const bool button_status = (command.buttons & (1U << i)) != 0;
212 const int button = static_cast<int>(i);
213 SetButton(identifier, button, button_status);
214 }
215 SetAxis(identifier, TasAxes::StickX, command.l_axis.x);
216 SetAxis(identifier, TasAxes::StickY, command.l_axis.y);
217 SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x);
218 SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y);
219 }
220 } else {
221 is_running = Settings::values.tas_loop.GetValue();
222 current_command = 0;
223 ClearInput();
224 }
225}
226
227void Tas::ClearInput() {
228 ResetButtonState();
229 ResetAnalogState();
230}
231
232TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
233 std::stringstream linestream(line);
234 std::string segment;
235 std::vector<std::string> seglist;
236
237 while (std::getline(linestream, segment, ';')) {
238 seglist.push_back(segment);
239 }
240
241 const float x = std::stof(seglist.at(0)) / 32767.0f;
242 const float y = std::stof(seglist.at(1)) / 32767.0f;
243
244 return {x, y};
245}
246
247u32 Tas::ReadCommandButtons(const std::string& data) const {
248 std::stringstream button_text(data);
249 std::string line;
250 u32 buttons = 0;
251 while (std::getline(button_text, line, ';')) {
252 for (auto [text, tas_button] : text_to_tas_button) {
253 if (text == line) {
254 buttons |= static_cast<u32>(tas_button);
255 break;
256 }
257 }
258 }
259 return buttons;
260}
261
262std::string Tas::WriteCommandButtons(u32 buttons) const {
263 std::string returns = "";
264 for (auto [text_button, tas_button] : text_to_tas_button) {
265 if ((buttons & static_cast<u32>(tas_button)) != 0)
266 returns += fmt::format("{};", text_button.substr(4));
267 }
268 return returns.empty() ? "NONE" : returns.substr(2);
269}
270
271std::string Tas::WriteCommandAxis(TasAnalog analog) const {
272 return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
273}
274
275void Tas::StartStop() {
276 if (!Settings::values.tas_enable) {
277 return;
278 }
279 if (is_running) {
280 Stop();
281 } else {
282 is_running = true;
283 }
284}
285
286void Tas::Stop() {
287 is_running = false;
288}
289
290void Tas::Reset() {
291 if (!Settings::values.tas_enable) {
292 return;
293 }
294 needs_reset = true;
295}
296
297bool Tas::Record() {
298 if (!Settings::values.tas_enable) {
299 return true;
300 }
301 is_recording = !is_recording;
302 return is_recording;
303}
304
305void Tas::SaveRecording(bool overwrite_file) {
306 if (is_recording) {
307 return;
308 }
309 if (record_commands.empty()) {
310 return;
311 }
312 WriteTasFile(u8"record.txt");
313 if (overwrite_file) {
314 WriteTasFile(u8"script0-1.txt");
315 }
316 needs_reset = true;
317 record_commands.clear();
318}
319
320} // namespace InputCommon::TasInput