tas_input.cpp 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. // Copyright 2021 yuzu Emulator Project
  2. // Licensed under GPLv2+
  3. // Refer to the license.txt file included.
  4. #include <cstring>
  5. #include <fmt/format.h>
  6. #include "common/fs/file.h"
  7. #include "common/fs/fs_types.h"
  8. #include "common/fs/path_util.h"
  9. #include "common/logging/log.h"
  10. #include "common/settings.h"
  11. #include "input_common/drivers/tas_input.h"
  12. namespace InputCommon::TasInput {
  13. enum class Tas::TasAxis : u8 {
  14. StickX,
  15. StickY,
  16. SubstickX,
  17. SubstickY,
  18. Undefined,
  19. };
  20. // Supported keywords and buttons from a TAS file
  21. constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = {
  22. std::pair{"KEY_A", TasButton::BUTTON_A},
  23. {"KEY_B", TasButton::BUTTON_B},
  24. {"KEY_X", TasButton::BUTTON_X},
  25. {"KEY_Y", TasButton::BUTTON_Y},
  26. {"KEY_LSTICK", TasButton::STICK_L},
  27. {"KEY_RSTICK", TasButton::STICK_R},
  28. {"KEY_L", TasButton::TRIGGER_L},
  29. {"KEY_R", TasButton::TRIGGER_R},
  30. {"KEY_PLUS", TasButton::BUTTON_PLUS},
  31. {"KEY_MINUS", TasButton::BUTTON_MINUS},
  32. {"KEY_DLEFT", TasButton::BUTTON_LEFT},
  33. {"KEY_DUP", TasButton::BUTTON_UP},
  34. {"KEY_DRIGHT", TasButton::BUTTON_RIGHT},
  35. {"KEY_DDOWN", TasButton::BUTTON_DOWN},
  36. {"KEY_SL", TasButton::BUTTON_SL},
  37. {"KEY_SR", TasButton::BUTTON_SR},
  38. {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE},
  39. {"KEY_HOME", TasButton::BUTTON_HOME},
  40. {"KEY_ZL", TasButton::TRIGGER_ZL},
  41. {"KEY_ZR", TasButton::TRIGGER_ZR},
  42. };
  43. Tas::Tas(const std::string& input_engine_) : InputCommon::InputEngine(input_engine_) {
  44. for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) {
  45. PadIdentifier identifier{
  46. .guid = Common::UUID{},
  47. .port = player_index,
  48. .pad = 0,
  49. };
  50. PreSetController(identifier);
  51. }
  52. ClearInput();
  53. if (!Settings::values.tas_enable) {
  54. needs_reset = true;
  55. return;
  56. }
  57. LoadTasFiles();
  58. }
  59. Tas::~Tas() {
  60. Stop();
  61. };
  62. void Tas::LoadTasFiles() {
  63. script_length = 0;
  64. for (size_t i = 0; i < commands.size(); i++) {
  65. LoadTasFile(i, 0);
  66. if (commands[i].size() > script_length) {
  67. script_length = commands[i].size();
  68. }
  69. }
  70. }
  71. void Tas::LoadTasFile(size_t player_index, size_t file_index) {
  72. if (!commands[player_index].empty()) {
  73. commands[player_index].clear();
  74. }
  75. std::string file = Common::FS::ReadStringFromFile(
  76. Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) /
  77. fmt::format("script{}-{}.txt", file_index, player_index + 1),
  78. Common::FS::FileType::BinaryFile);
  79. std::istringstream command_line(file);
  80. std::string line;
  81. int frame_no = 0;
  82. while (std::getline(command_line, line, '\n')) {
  83. if (line.empty()) {
  84. continue;
  85. }
  86. std::istringstream linestream(line);
  87. std::string segment;
  88. std::vector<std::string> seglist;
  89. while (std::getline(linestream, segment, ' ')) {
  90. seglist.push_back(segment);
  91. }
  92. if (seglist.size() < 4) {
  93. continue;
  94. }
  95. while (frame_no < std::stoi(seglist.at(0))) {
  96. commands[player_index].push_back({});
  97. frame_no++;
  98. }
  99. TASCommand command = {
  100. .buttons = ReadCommandButtons(seglist.at(1)),
  101. .l_axis = ReadCommandAxis(seglist.at(2)),
  102. .r_axis = ReadCommandAxis(seglist.at(3)),
  103. };
  104. commands[player_index].push_back(command);
  105. frame_no++;
  106. }
  107. LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
  108. }
  109. void Tas::WriteTasFile(std::u8string_view file_name) {
  110. std::string output_text;
  111. for (size_t frame = 0; frame < record_commands.size(); frame++) {
  112. const TASCommand& line = record_commands[frame];
  113. output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons),
  114. WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis));
  115. }
  116. const auto tas_file_name = Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name;
  117. const auto bytes_written =
  118. Common::FS::WriteStringToFile(tas_file_name, Common::FS::FileType::TextFile, output_text);
  119. if (bytes_written == output_text.size()) {
  120. LOG_INFO(Input, "TAS file written to file!");
  121. } else {
  122. LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
  123. output_text.size());
  124. }
  125. }
  126. void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) {
  127. last_input = {
  128. .buttons = buttons,
  129. .l_axis = left_axis,
  130. .r_axis = right_axis,
  131. };
  132. }
  133. std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
  134. TasState state;
  135. if (is_recording) {
  136. return {TasState::Recording, 0, record_commands.size()};
  137. }
  138. if (is_running) {
  139. state = TasState::Running;
  140. } else {
  141. state = TasState::Stopped;
  142. }
  143. return {state, current_command, script_length};
  144. }
  145. void Tas::UpdateThread() {
  146. if (!Settings::values.tas_enable) {
  147. if (is_running) {
  148. Stop();
  149. }
  150. return;
  151. }
  152. if (is_recording) {
  153. record_commands.push_back(last_input);
  154. }
  155. if (needs_reset) {
  156. current_command = 0;
  157. needs_reset = false;
  158. LoadTasFiles();
  159. LOG_DEBUG(Input, "tas_reset done");
  160. }
  161. if (!is_running) {
  162. ClearInput();
  163. return;
  164. }
  165. if (current_command < script_length) {
  166. LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length);
  167. const size_t frame = current_command++;
  168. for (size_t player_index = 0; player_index < commands.size(); player_index++) {
  169. TASCommand command{};
  170. if (frame < commands[player_index].size()) {
  171. command = commands[player_index][frame];
  172. }
  173. PadIdentifier identifier{
  174. .guid = Common::UUID{},
  175. .port = player_index,
  176. .pad = 0,
  177. };
  178. for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) {
  179. const bool button_status = (command.buttons & (1LLU << i)) != 0;
  180. const int button = static_cast<int>(i);
  181. SetButton(identifier, button, button_status);
  182. }
  183. SetTasAxis(identifier, TasAxis::StickX, command.l_axis.x);
  184. SetTasAxis(identifier, TasAxis::StickY, command.l_axis.y);
  185. SetTasAxis(identifier, TasAxis::SubstickX, command.r_axis.x);
  186. SetTasAxis(identifier, TasAxis::SubstickY, command.r_axis.y);
  187. }
  188. } else {
  189. is_running = Settings::values.tas_loop.GetValue();
  190. LoadTasFiles();
  191. current_command = 0;
  192. ClearInput();
  193. }
  194. }
  195. void Tas::ClearInput() {
  196. ResetButtonState();
  197. ResetAnalogState();
  198. }
  199. TasAnalog Tas::ReadCommandAxis(const std::string& line) const {
  200. std::stringstream linestream(line);
  201. std::string segment;
  202. std::vector<std::string> seglist;
  203. while (std::getline(linestream, segment, ';')) {
  204. seglist.push_back(segment);
  205. }
  206. const float x = std::stof(seglist.at(0)) / 32767.0f;
  207. const float y = std::stof(seglist.at(1)) / 32767.0f;
  208. return {x, y};
  209. }
  210. u64 Tas::ReadCommandButtons(const std::string& line) const {
  211. std::stringstream button_text(line);
  212. std::string button_line;
  213. u64 buttons = 0;
  214. while (std::getline(button_text, button_line, ';')) {
  215. for (auto [text, tas_button] : text_to_tas_button) {
  216. if (text == button_line) {
  217. buttons |= static_cast<u64>(tas_button);
  218. break;
  219. }
  220. }
  221. }
  222. return buttons;
  223. }
  224. std::string Tas::WriteCommandButtons(u64 buttons) const {
  225. std::string returns;
  226. for (auto [text_button, tas_button] : text_to_tas_button) {
  227. if ((buttons & static_cast<u64>(tas_button)) != 0) {
  228. returns += fmt::format("{};", text_button);
  229. }
  230. }
  231. return returns.empty() ? "NONE" : returns;
  232. }
  233. std::string Tas::WriteCommandAxis(TasAnalog analog) const {
  234. return fmt::format("{};{}", analog.x * 32767, analog.y * 32767);
  235. }
  236. void Tas::SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value) {
  237. SetAxis(identifier, static_cast<int>(axis), value);
  238. }
  239. void Tas::StartStop() {
  240. if (!Settings::values.tas_enable) {
  241. return;
  242. }
  243. if (is_running) {
  244. Stop();
  245. } else {
  246. is_running = true;
  247. }
  248. }
  249. void Tas::Stop() {
  250. is_running = false;
  251. }
  252. void Tas::Reset() {
  253. if (!Settings::values.tas_enable) {
  254. return;
  255. }
  256. needs_reset = true;
  257. }
  258. bool Tas::Record() {
  259. if (!Settings::values.tas_enable) {
  260. return true;
  261. }
  262. is_recording = !is_recording;
  263. return is_recording;
  264. }
  265. void Tas::SaveRecording(bool overwrite_file) {
  266. if (is_recording) {
  267. return;
  268. }
  269. if (record_commands.empty()) {
  270. return;
  271. }
  272. WriteTasFile(u8"record.txt");
  273. if (overwrite_file) {
  274. WriteTasFile(u8"script0-1.txt");
  275. }
  276. needs_reset = true;
  277. record_commands.clear();
  278. }
  279. } // namespace InputCommon::TasInput