Просмотр исходного кода

config: Move TAS options to it's own menu

german77 5 лет назад
Родитель
Сommit
c01a872c8e

+ 1 - 1
src/common/fs/fs_paths.h

@@ -21,7 +21,7 @@
 #define SCREENSHOTS_DIR "screenshots"
 #define SDMC_DIR "sdmc"
 #define SHADER_DIR "shader"
-#define TAS_DIR "scripts"
+#define TAS_DIR "tas"
 
 // yuzu-specific files
 

+ 1 - 1
src/common/fs/path_util.cpp

@@ -116,7 +116,7 @@ private:
         GenerateYuzuPath(YuzuPath::ScreenshotsDir, yuzu_path / SCREENSHOTS_DIR);
         GenerateYuzuPath(YuzuPath::SDMCDir, yuzu_path / SDMC_DIR);
         GenerateYuzuPath(YuzuPath::ShaderDir, yuzu_path / SHADER_DIR);
-        GenerateYuzuPath(YuzuPath::TASFile, yuzu_path / TAS_DIR);
+        GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR);
     }
 
     ~PathManagerImpl() = default;

+ 1 - 2
src/common/fs/path_util.h

@@ -23,8 +23,7 @@ enum class YuzuPath {
     ScreenshotsDir, // Where yuzu screenshots are stored.
     SDMCDir,        // Where the emulated SDMC is stored.
     ShaderDir,      // Where shaders are stored.
-
-    TASFile, // Where the current script file is stored.
+    TASDir,         // Where the current script file is stored.
 };
 
 /**

+ 1 - 1
src/core/hle/service/apm/apm_interface.cpp

@@ -121,7 +121,7 @@ void APM_Sys::SetCpuBoostMode(Kernel::HLERequestContext& ctx) {
 
     LOG_DEBUG(Service_APM, "called, mode={:08X}", mode);
 
-    Settings::values.is_cpu_boosted = (static_cast<u32>(mode) == 1);
+    Settings::values.is_cpu_boosted = (mode == CpuBoostMode::Full);
     controller.SetFromCpuBoostMode(mode);
 
     IPC::ResponseBuilder rb{ctx, 2};

+ 5 - 1
src/input_common/main.cpp

@@ -5,6 +5,7 @@
 #include <memory>
 #include <thread>
 #include "common/param_package.h"
+#include "common/settings.h"
 #include "input_common/analog_from_button.h"
 #include "input_common/gcadapter/gc_adapter.h"
 #include "input_common/gcadapter/gc_poller.h"
@@ -114,8 +115,11 @@ struct InputSubsystem::Impl {
         std::vector<Common::ParamPackage> devices = {
             Common::ParamPackage{{"display", "Any"}, {"class", "any"}},
             Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}},
-            Common::ParamPackage{{"display", "TAS"}, {"class", "tas"}},
         };
+        if (Settings::values.tas_enable) {
+            devices.push_back(
+                Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}});
+        }
 #ifdef HAVE_SDL2
         auto sdl_devices = sdl->GetInputDevices();
         devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());

+ 105 - 84
src/input_common/tas/tas_input.cpp

@@ -67,14 +67,13 @@ void Tas::LoadTasFile(size_t player_index) {
     if (!commands[player_index].empty()) {
         commands[player_index].clear();
     }
-    std::string file = Common::FS::ReadStringFromFile(
-        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "script0-" +
-            std::to_string(player_index + 1) + ".txt",
-        Common::FS::FileType::BinaryFile);
+    std::string file =
+        Common::FS::ReadStringFromFile(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) +
+                                           "script0-" + std::to_string(player_index + 1) + ".txt",
+                                       Common::FS::FileType::BinaryFile);
     std::stringstream command_line(file);
     std::string line;
-    int frameNo = 0;
-    TASCommand empty = {.buttons = 0, .l_axis = {0.f, 0.f}, .r_axis = {0.f, 0.f}};
+    int frame_no = 0;
     while (std::getline(command_line, line, '\n')) {
         if (line.empty()) {
             continue;
@@ -94,9 +93,9 @@ void Tas::LoadTasFile(size_t player_index) {
             continue;
         }
 
-        while (frameNo < std::stoi(seglist.at(0))) {
-            commands[player_index].push_back(empty);
-            frameNo++;
+        while (frame_no < std::stoi(seglist.at(0))) {
+            commands[player_index].push_back({});
+            frame_no++;
         }
 
         TASCommand command = {
@@ -105,30 +104,29 @@ void Tas::LoadTasFile(size_t player_index) {
             .r_axis = ReadCommandAxis(seglist.at(3)),
         };
         commands[player_index].push_back(command);
-        frameNo++;
+        frame_no++;
     }
-    LOG_INFO(Input, "TAS file loaded! {} frames", frameNo);
+    LOG_INFO(Input, "TAS file loaded! {} frames", frame_no);
 }
 
 void Tas::WriteTasFile() {
     LOG_DEBUG(Input, "WriteTasFile()");
-    std::string output_text = "";
-    for (int frame = 0; frame < (signed)record_commands.size(); frame++) {
+    std::string output_text;
+    for (size_t frame = 0; frame < record_commands.size(); frame++) {
         if (!output_text.empty()) {
             output_text += "\n";
         }
-        TASCommand line = record_commands.at(frame);
+        const TASCommand& line = record_commands[frame];
         output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " +
                        WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis);
     }
-    size_t bytesWritten = Common::FS::WriteStringToFile(
-        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile) + "record.txt",
+    const size_t bytes_written = Common::FS::WriteStringToFile(
+        Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir) + "record.txt",
         Common::FS::FileType::TextFile, output_text);
-    if (bytesWritten == output_text.size()) {
+    if (bytes_written == output_text.size()) {
         LOG_INFO(Input, "TAS file written to file!");
-    }
-    else {
-        LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytesWritten,
+    } else {
+        LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written,
                   output_text.size());
     }
 }
@@ -142,30 +140,33 @@ void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>&
     last_input = {buttons, FlipY(axes[0]), FlipY(axes[1])};
 }
 
-std::tuple<TasState, size_t, size_t> Tas::GetStatus() {
+std::tuple<TasState, size_t, size_t> Tas::GetStatus() const {
     TasState state;
-    if (Settings::values.tas_record) {
-        return {TasState::RECORDING, record_commands.size(), record_commands.size()};
-    } else if (Settings::values.tas_enable) {
-        state = TasState::RUNNING;
+    if (is_recording) {
+        return {TasState::Recording, 0, record_commands.size()};
+    }
+
+    if (is_running) {
+        state = TasState::Running;
     } else {
-        state = TasState::STOPPED;
+        state = TasState::Stopped;
     }
 
     return {state, current_command, script_length};
 }
 
 static std::string DebugButtons(u32 buttons) {
-    return "{ " + TasInput::Tas::ButtonsToString(buttons) + " }";
+    return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons));
 }
 
 static std::string DebugJoystick(float x, float y) {
-    return "[ " + std::to_string(x) + "," + std::to_string(y) + " ]";
+    return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y));
 }
 
 static std::string DebugInput(const TasData& data) {
-    return "{ " + DebugButtons(data.buttons) + " , " + DebugJoystick(data.axis[0], data.axis[1]) +
-           " , " + DebugJoystick(data.axis[2], data.axis[3]) + " }";
+    return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons),
+                       DebugJoystick(data.axis[0], data.axis[1]),
+                       DebugJoystick(data.axis[2], data.axis[3]));
 }
 
 static std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) {
@@ -180,66 +181,54 @@ static std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) {
 }
 
 void Tas::UpdateThread() {
-    if (update_thread_running) {
-        if (Settings::values.pause_tas_on_load && Settings::values.is_cpu_boosted) {
-            for (size_t i = 0; i < PLAYER_NUMBER; i++) {
-                tas_data[i].buttons = 0;
-                tas_data[i].axis = {};
-            }
-        }
+    if (!update_thread_running) {
+        return;
+    }
 
-        if (Settings::values.tas_record) {
-            record_commands.push_back(last_input);
-        }
-        if (!Settings::values.tas_record && !record_commands.empty()) {
-            WriteTasFile();
-            Settings::values.tas_reset = true;
-            refresh_tas_fle = true;
-            record_commands.clear();
-        }
-        if (Settings::values.tas_reset) {
-            current_command = 0;
-            if (refresh_tas_fle) {
-                LoadTasFiles();
-                refresh_tas_fle = false;
-            }
-            Settings::values.tas_reset = false;
+    if (is_recording) {
+        record_commands.push_back(last_input);
+    }
+    if (!is_recording && !record_commands.empty()) {
+        WriteTasFile();
+        needs_reset = true;
+        refresh_tas_fle = true;
+        record_commands.clear();
+    }
+    if (needs_reset) {
+        current_command = 0;
+        if (refresh_tas_fle) {
             LoadTasFiles();
-            LOG_DEBUG(Input, "tas_reset done");
+            refresh_tas_fle = false;
         }
-        if (Settings::values.tas_enable) {
-            if ((signed)current_command < script_length) {
-                LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length);
-                size_t frame = current_command++;
-                for (size_t i = 0; i < PLAYER_NUMBER; i++) {
-                    if (frame < commands[i].size()) {
-                        TASCommand command = commands[i][frame];
-                        tas_data[i].buttons = command.buttons;
-                        auto [l_axis_x, l_axis_y] = command.l_axis;
-                        tas_data[i].axis[0] = l_axis_x;
-                        tas_data[i].axis[1] = l_axis_y;
-                        auto [r_axis_x, r_axis_y] = command.r_axis;
-                        tas_data[i].axis[2] = r_axis_x;
-                        tas_data[i].axis[3] = r_axis_y;
-                    } else {
-                        tas_data[i].buttons = 0;
-                        tas_data[i].axis = {};
-                    }
-                }
-            } else {
-                Settings::values.tas_enable = false;
-                current_command = 0;
-                for (size_t i = 0; i < PLAYER_NUMBER; i++) {
-                    tas_data[i].buttons = 0;
-                    tas_data[i].axis = {};
+        needs_reset = false;
+        LoadTasFiles();
+        LOG_DEBUG(Input, "tas_reset done");
+    }
+    if (is_running) {
+        if (current_command < script_length) {
+            LOG_INFO(Input, "Playing TAS {}/{}", current_command, script_length);
+            size_t frame = current_command++;
+            for (size_t i = 0; i < PLAYER_NUMBER; i++) {
+                if (frame < commands[i].size()) {
+                    TASCommand command = commands[i][frame];
+                    tas_data[i].buttons = command.buttons;
+                    auto [l_axis_x, l_axis_y] = command.l_axis;
+                    tas_data[i].axis[0] = l_axis_x;
+                    tas_data[i].axis[1] = l_axis_y;
+                    auto [r_axis_x, r_axis_y] = command.r_axis;
+                    tas_data[i].axis[2] = r_axis_x;
+                    tas_data[i].axis[3] = r_axis_y;
+                } else {
+                    tas_data[i] = {};
                 }
             }
         } else {
-            for (size_t i = 0; i < PLAYER_NUMBER; i++) {
-                tas_data[i].buttons = 0;
-                tas_data[i].axis = {};
-            }
+            is_running = Settings::values.tas_loop;
+            current_command = 0;
+            tas_data.fill({});
         }
+    } else {
+        tas_data.fill({});
     }
     LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data));
 }
@@ -284,8 +273,9 @@ std::string Tas::WriteCommandAxis(TasAnalog data) const {
 }
 
 std::string Tas::WriteCommandButtons(u32 data) const {
-    if (data == 0)
+    if (data == 0) {
         return "NONE";
+    }
 
     std::string line;
     u32 index = 0;
@@ -307,6 +297,37 @@ std::string Tas::WriteCommandButtons(u32 data) const {
     return line;
 }
 
+void Tas::StartStop() {
+    is_running = !is_running;
+}
+
+void Tas::Reset() {
+    needs_reset = true;
+}
+
+void Tas::Record() {
+    is_recording = !is_recording;
+<<<<<<< HEAD
+=======
+    return is_recording;
+}
+
+void Tas::SaveRecording(bool overwrite_file) {
+    if (is_recording) {
+        return;
+    }
+    if (record_commands.empty()) {
+        return;
+    }
+    WriteTasFile("record.txt");
+    if (overwrite_file) {
+        WriteTasFile("script0-1.txt");
+    }
+    needs_reset = true;
+    record_commands.clear();
+>>>>>>> 773d268db (config: disable pause on load)
+}
+
 InputCommon::ButtonMapping Tas::GetButtonMappingForDevice(
     const Common::ParamPackage& params) const {
     // This list is missing ZL/ZR since those are not considered buttons.

+ 20 - 6
src/input_common/tas/tas_input.h

@@ -14,14 +14,14 @@
 
 namespace TasInput {
 
-constexpr int PLAYER_NUMBER = 8;
+constexpr size_t PLAYER_NUMBER = 8;
 
 using TasAnalog = std::pair<float, float>;
 
 enum class TasState {
-    RUNNING,
-    RECORDING,
-    STOPPED,
+    Running,
+    Recording,
+    Stopped,
 };
 
 enum class TasButton : u32 {
@@ -114,8 +114,19 @@ public:
     void LoadTasFiles();
     void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes);
     void UpdateThread();
-    std::tuple<TasState, size_t, size_t> GetStatus();
 
+    void StartStop();
+    void Reset();
+    void Record();
+
+    /**
+     * Returns the current status values of TAS playback/recording
+     * @return Tuple of
+     * TasState indicating the current state out of Running, Recording or Stopped ;
+     * Current playback progress or amount of frames (so far) for Recording ;
+     * Total length of script file currently loaded or amount of frames (so far) for Recording
+     */
+    std::tuple<TasState, size_t, size_t> GetStatus() const;
     InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const;
     InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const;
     [[nodiscard]] const TasData& GetTasState(std::size_t pad) const;
@@ -137,9 +148,12 @@ private:
     std::array<TasData, PLAYER_NUMBER> tas_data;
     bool update_thread_running{true};
     bool refresh_tas_fle{false};
+    bool is_recording{false};
+    bool is_running{false};
+    bool needs_reset{false};
     std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{};
     std::vector<TASCommand> record_commands{};
-    std::size_t current_command{0};
+    size_t current_command{0};
     TASCommand last_input{}; // only used for recording
 };
 } // namespace TasInput

+ 3 - 0
src/yuzu/CMakeLists.txt

@@ -108,6 +108,9 @@ add_executable(yuzu
     configuration/configure_system.cpp
     configuration/configure_system.h
     configuration/configure_system.ui
+    configuration/configure_tas.cpp
+    configuration/configure_tas.h
+    configuration/configure_tas.ui
     configuration/configure_touch_from_button.cpp
     configuration/configure_touch_from_button.h
     configuration/configure_touch_from_button.ui

+ 0 - 9
src/yuzu/configuration/configure_filesystem.cpp

@@ -26,8 +26,6 @@ ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
             [this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); });
     connect(ui->load_path_button, &QToolButton::pressed, this,
             [this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); });
-    connect(ui->tas_path_button, &QToolButton::pressed, this,
-            [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); });
 
     connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
             &ConfigureFilesystem::ResetMetadata);
@@ -51,8 +49,6 @@ void ConfigureFilesystem::setConfiguration() {
         QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::DumpDir)));
     ui->load_path_edit->setText(
         QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::LoadDir)));
-    ui->tas_path_edit->setText(
-        QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASFile)));
 
     ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted.GetValue());
     ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game.GetValue());
@@ -74,11 +70,9 @@ void ConfigureFilesystem::applyConfiguration() {
                             ui->dump_path_edit->text().toStdString());
     Common::FS::SetYuzuPath(Common::FS::YuzuPath::LoadDir,
                             ui->load_path_edit->text().toStdString());
-    Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASFile, ui->tas_path_edit->text().toStdString());
 
     Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
     Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
-    Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked();
     Settings::values.dump_exefs = ui->dump_exefs->isChecked();
     Settings::values.dump_nso = ui->dump_nso->isChecked();
 
@@ -104,9 +98,6 @@ void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit)
     case DirectoryTarget::Load:
         caption = tr("Select Mod Load Directory...");
         break;
-    case DirectoryTarget::TAS:
-        caption = tr("Select TAS Directory...");
-        break;
     }
 
     QString str;

+ 0 - 1
src/yuzu/configuration/configure_filesystem.h

@@ -32,7 +32,6 @@ private:
         Gamecard,
         Dump,
         Load,
-        TAS,
     };
 
     void SetDirectory(DirectoryTarget target, QLineEdit* edit);

+ 0 - 49
src/yuzu/configuration/configure_filesystem.ui

@@ -219,55 +219,6 @@
        </layout>
       </widget>
      </item>
-      <item>
-        <widget class="QGroupBox" name="groupBox">
-          <property name="title">
-            <string>TAS Directories</string>
-          </property>
-          <layout class="QGridLayout" name="gridLayout">
-            <item row="0" column="0">
-              <widget class="QLabel" name="label">
-                <property name="text">
-                  <string>Path</string>
-                </property>
-              </widget>
-            </item>
-            <item row="0" column="3">
-              <widget class="QToolButton" name="tas_path_button">
-                <property name="text">
-                  <string>...</string>
-                </property>
-              </widget>
-            </item>
-            <item row="0" column="2">
-              <widget class="QLineEdit" name="tas_path_edit"/>
-            </item>
-            <item row="0" column="1">
-              <spacer name="horizontalSpacer">
-                <property name="orientation">
-                  <enum>Qt::Horizontal</enum>
-                </property>
-                <property name="sizeType">
-                  <enum>QSizePolicy::Maximum</enum>
-                </property>
-                <property name="sizeHint" stdset="0">
-                  <size>
-                    <width>60</width>
-                    <height>20</height>
-                  </size>
-                </property>
-              </spacer>
-            </item>
-            <item row="1" column="0" colspan="4">
-              <widget class="QCheckBox" name="tas_pause_on_load">
-                <property name="text">
-                  <string>Pause TAS execution during loads (SMO - 1.3)</string>
-                </property>
-              </widget>
-            </item>
-          </layout>
-        </widget>
-      </item>
     </layout>
    </item>
    <item>

+ 2 - 2
src/yuzu/configuration/configure_input_player_widget.cpp

@@ -232,7 +232,7 @@ void PlayerControlPreview::UpdateInput() {
         axis_values[Settings::NativeAnalog::RStick].value.x(),
         axis_values[Settings::NativeAnalog::RStick].value.y()};
     input.button_values = button_values;
-    if (controller_callback.input != NULL) {
+    if (controller_callback.input != nullptr) {
         controller_callback.input(std::move(input));
     }
 
@@ -242,7 +242,7 @@ void PlayerControlPreview::UpdateInput() {
 }
 
 void PlayerControlPreview::SetCallBack(ControllerCallback callback_) {
-    controller_callback = callback_;
+    controller_callback = std::move(callback_);
 }
 
 void PlayerControlPreview::paintEvent(QPaintEvent* event) {

+ 84 - 0
src/yuzu/configuration/configure_tas.cpp

@@ -0,0 +1,84 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QFileDialog>
+#include <QMessageBox>
+#include "common/fs/fs.h"
+#include "common/fs/path_util.h"
+#include "common/settings.h"
+#include "ui_configure_tas.h"
+#include "yuzu/configuration/configure_tas.h"
+#include "yuzu/uisettings.h"
+
+ConfigureTasDialog::ConfigureTasDialog(QWidget* parent)
+    : QDialog(parent), ui(std::make_unique<Ui::ConfigureTas>()) {
+
+    ui->setupUi(this);
+
+    setFocusPolicy(Qt::ClickFocus);
+    setWindowTitle(tr("TAS Configuration"));
+
+    connect(ui->tas_path_button, &QToolButton::pressed, this,
+            [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); });
+
+    LoadConfiguration();
+}
+
+ConfigureTasDialog::~ConfigureTasDialog() = default;
+
+void ConfigureTasDialog::LoadConfiguration() {
+    ui->tas_path_edit->setText(
+        QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::TASDir)));
+    ui->tas_enable->setChecked(Settings::values.tas_enable);
+    ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers);
+    ui->tas_loop_script->setChecked(Settings::values.tas_loop);
+    ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load);
+}
+
+void ConfigureTasDialog::ApplyConfiguration() {
+    Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString());
+    Settings::values.tas_enable = ui->tas_enable->isChecked();
+    Settings::values.tas_swap_controllers = ui->tas_control_swap->isChecked();
+    Settings::values.tas_loop = ui->tas_loop_script->isChecked();
+    Settings::values.pause_tas_on_load = ui->tas_pause_on_load->isChecked();
+}
+
+void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
+    QString caption;
+
+    switch (target) {
+    case DirectoryTarget::TAS:
+        caption = tr("Select TAS Load Directory...");
+        break;
+    }
+
+    QString str = QFileDialog::getExistingDirectory(this, caption, edit->text());
+
+    if (str.isNull() || str.isEmpty()) {
+        return;
+    }
+
+    if (str.back() != QChar::fromLatin1('/')) {
+        str.append(QChar::fromLatin1('/'));
+    }
+
+    edit->setText(str);
+}
+
+void ConfigureTasDialog::changeEvent(QEvent* event) {
+    if (event->type() == QEvent::LanguageChange) {
+        RetranslateUI();
+    }
+
+    QDialog::changeEvent(event);
+}
+
+void ConfigureTasDialog::RetranslateUI() {
+    ui->retranslateUi(this);
+}
+
+void ConfigureTasDialog::HandleApplyButtonClicked() {
+    UISettings::values.configuration_applied = true;
+    ApplyConfiguration();
+}

+ 38 - 0
src/yuzu/configuration/configure_tas.h

@@ -0,0 +1,38 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDialog>
+
+namespace Ui {
+class ConfigureTas;
+}
+
+class ConfigureTasDialog : public QDialog {
+    Q_OBJECT
+
+public:
+    explicit ConfigureTasDialog(QWidget* parent);
+    ~ConfigureTasDialog() override;
+
+    /// Save all button configurations to settings file
+    void ApplyConfiguration();
+
+private:
+    enum class DirectoryTarget {
+        TAS,
+    };
+
+    void LoadConfiguration();
+
+    void SetDirectory(DirectoryTarget target, QLineEdit* edit);
+
+    void changeEvent(QEvent* event) override;
+    void RetranslateUI();
+
+    void HandleApplyButtonClicked();
+
+    std::unique_ptr<Ui::ConfigureTas> ui;
+};

+ 143 - 0
src/yuzu/configuration/configure_tas.ui

@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+  <class>ConfigureTas</class>
+  <widget class="QDialog" name="ConfigureTas">
+    <property name="geometry">
+      <rect>
+        <x>0</x>
+        <y>0</y>
+        <width>800</width>
+        <height>300</height>
+      </rect>
+    </property>
+    <property name="windowTitle">
+      <string>Dialog</string>
+    </property>
+    <layout class="QVBoxLayout" name="verticalLayout_1">
+      <item>
+        <layout class="QHBoxLayout" name="horizontalLayout">
+          <item>
+            <widget class="QGroupBox" name="groupBox">
+              <property name="title">
+                <string>TAS Settings</string>
+              </property>
+              <layout class="QGridLayout" name="gridLayout">
+                <item row="0" column="0" colspan="4">
+                  <widget class="QCheckBox" name="tas_enable">
+                    <property name="text">
+                      <string>Enable TAS features</string>
+                    </property>
+                  </widget>
+                </item>
+                <item row="1" column="0" colspan="4">
+                  <widget class="QCheckBox" name="tas_control_swap">
+                    <property name="enabled">
+                      <bool>false</bool>
+                    </property>
+                    <property name="text">
+                      <string>Automatic controller profile swapping</string>
+                    </property>
+                  </widget>
+                </item>
+                <item row="2" column="0" colspan="4">
+                  <widget class="QCheckBox" name="tas_loop_script">
+                    <property name="text">
+                      <string>Loop script</string>
+                    </property>
+                  </widget>
+                </item>
+                <item row="3" column="0" colspan="4">
+                  <widget class="QCheckBox" name="tas_pause_on_load">
+                    <property name="enabled">
+                      <bool>false</bool>
+                    </property>
+                    <property name="text">
+                      <string>Pause execution during loads</string>
+                    </property>
+                  </widget>
+                </item>
+              </layout>
+            </widget>
+          </item>
+        </layout>
+      </item>
+      <item>
+        <layout class="QHBoxLayout" name="horizontalLayout">
+          <item>
+            <widget class="QGroupBox" name="groupBox">
+              <property name="title">
+                <string>TAS Directories</string>
+              </property>
+              <layout class="QGridLayout" name="gridLayout">
+                <item row="0" column="0">
+                  <widget class="QLabel" name="label">
+                    <property name="text">
+                      <string>Path</string>
+                    </property>
+                  </widget>
+                </item>
+                <item row="0" column="3">
+                  <widget class="QToolButton" name="tas_path_button">
+                    <property name="text">
+                      <string>...</string>
+                    </property>
+                  </widget>
+                </item>
+                <item row="0" column="2">
+                  <widget class="QLineEdit" name="tas_path_edit"/>
+                </item>
+                <item row="0" column="1">
+                  <spacer name="horizontalSpacer">
+                    <property name="orientation">
+                      <enum>Qt::Horizontal</enum>
+                    </property>
+                    <property name="sizeType">
+                      <enum>QSizePolicy::Maximum</enum>
+                    </property>
+                    <property name="sizeHint" stdset="0">
+                      <size>
+                        <width>60</width>
+                        <height>20</height>
+                      </size>
+                    </property>
+                  </spacer>
+                </item>
+              </layout>
+            </widget>
+          </item>
+        </layout>
+      </item>
+      <item>
+        <widget class="QDialogButtonBox" name="buttonBox">
+          <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+            </sizepolicy>
+          </property>
+          <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="standardButtons">
+            <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+          </property>
+        </widget>
+      </item>
+    </layout>
+  </widget>
+  <resources/>
+  <connections>
+    <connection>
+      <sender>buttonBox</sender>
+      <signal>accepted()</signal>
+      <receiver>ConfigureTas</receiver>
+      <slot>accept()</slot>
+    </connection>
+    <connection>
+      <sender>buttonBox</sender>
+      <signal>rejected()</signal>
+      <receiver>ConfigureTas</receiver>
+      <slot>reject()</slot>
+    </connection>
+  </connections>
+</ui>

+ 0 - 1
src/yuzu/debugger/controller.h

@@ -25,7 +25,6 @@ struct ControllerInput {
 
 struct ControllerCallback {
     std::function<void(ControllerInput)> input;
-    std::function<void()> update;
 };
 
 class ControllerDialog : public QWidget {

+ 41 - 26
src/yuzu/main.cpp

@@ -19,6 +19,7 @@
 #include "common/nvidia_flags.h"
 #include "configuration/configure_input.h"
 #include "configuration/configure_per_game.h"
+#include "configuration/configure_tas.h"
 #include "configuration/configure_vibration.h"
 #include "core/file_sys/vfs.h"
 #include "core/file_sys/vfs_real.h"
@@ -750,6 +751,11 @@ void GMainWindow::InitializeWidgets() {
         statusBar()->addPermanentWidget(label);
     }
 
+    tas_label = new QLabel();
+    tas_label->setObjectName(QStringLiteral("TASlabel"));
+    tas_label->setFocusPolicy(Qt::NoFocus);
+    statusBar()->insertPermanentWidget(0, tas_label);
+
     // Setup Dock button
     dock_status_button = new QPushButton();
     dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton"));
@@ -826,12 +832,6 @@ void GMainWindow::InitializeWidgets() {
     });
     statusBar()->insertPermanentWidget(0, renderer_status_button);
 
-    tas_label = new QLabel();
-    tas_label->setObjectName(QStringLiteral("TASlabel"));
-    tas_label->setText(tr("TAS not running"));
-    tas_label->setFocusPolicy(Qt::NoFocus);
-    statusBar()->insertPermanentWidget(0, tas_label);
-
     statusBar()->setVisible(true);
     setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}"));
 }
@@ -1024,18 +1024,11 @@ void GMainWindow::InitializeHotkeys() {
                 }
             });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this),
-            &QShortcut::activated, this, [&] {
-                Settings::values.tas_enable = !Settings::values.tas_enable;
-                LOG_INFO(Frontend, "Tas enabled {}", Settings::values.tas_enable);
-            });
-
+            &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this),
-            &QShortcut::activated, this, [&] { Settings::values.tas_reset = true; });
+            &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); });
     connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this),
-            &QShortcut::activated, this, [&] {
-                Settings::values.tas_record = !Settings::values.tas_record;
-                LOG_INFO(Frontend, "Tas recording {}", Settings::values.tas_record);
-            });
+            &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Record(); });
 }
 
 void GMainWindow::SetDefaultUIGeometry() {
@@ -1154,6 +1147,7 @@ void GMainWindow::ConnectMenuEvents() {
     connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ);
     connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); });
     connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
+    connect(ui.action_Configure_Tas, &QAction::triggered, this, &GMainWindow::OnConfigureTas);
     connect(ui.action_Configure_Current_Game, &QAction::triggered, this,
             &GMainWindow::OnConfigurePerGame);
 
@@ -2720,6 +2714,19 @@ void GMainWindow::OnConfigure() {
     UpdateStatusButtons();
 }
 
+void GMainWindow::OnConfigureTas() {
+    const auto& system = Core::System::GetInstance();
+    ConfigureTasDialog dialog(this);
+    const auto result = dialog.exec();
+
+    if (result != QDialog::Accepted && !UISettings::values.configuration_applied) {
+        Settings::RestoreGlobalState(system.IsPoweredOn());
+        return;
+    } else if (result == QDialog::Accepted) {
+        dialog.ApplyConfiguration();
+    }
+}
+
 void GMainWindow::OnConfigurePerGame() {
     const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
     OpenPerGameConfiguration(title_id, game_path.toStdString());
@@ -2898,14 +2905,14 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie
 
 static std::string GetTasStateDescription(TasInput::TasState state) {
     switch (state) {
-        case TasInput::TasState::RUNNING:
-            return "Running";
-        case TasInput::TasState::RECORDING:
-            return "Recording";
-        case TasInput::TasState::STOPPED:
-            return "Stopped";
-        default:
-            return "INVALID STATE";
+    case TasInput::TasState::Running:
+        return "Running";
+    case TasInput::TasState::Recording:
+        return "Recording";
+    case TasInput::TasState::Stopped:
+        return "Stopped";
+    default:
+        return "INVALID STATE";
     }
 }
 
@@ -2915,8 +2922,16 @@ void GMainWindow::UpdateStatusBar() {
         return;
     }
 
-    auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus();
-    tas_label->setText(tr("%1 TAS %2/%3").arg(tr(GetTasStateDescription(tas_status).c_str())).arg(current_tas_frame).arg(total_tas_frames));
+    if (Settings::values.tas_enable) {
+        auto [tas_status, current_tas_frame, total_tas_frames] =
+            input_subsystem->GetTas()->GetStatus();
+        tas_label->setText(tr("%1 TAS %2/%3")
+                               .arg(tr(GetTasStateDescription(tas_status).c_str()))
+                               .arg(current_tas_frame)
+                               .arg(total_tas_frames));
+    } else {
+        tas_label->clear();
+    }
 
     auto& system = Core::System::GetInstance();
     auto results = system.GetAndResetPerfStats();

+ 1 - 0
src/yuzu/main.h

@@ -259,6 +259,7 @@ private slots:
     void OnMenuInstallToNAND();
     void OnMenuRecentFile();
     void OnConfigure();
+    void OnConfigureTas();
     void OnConfigurePerGame();
     void OnLoadAmiibo();
     void OnOpenYuzuFolder();

+ 6 - 0
src/yuzu/main.ui

@@ -72,6 +72,7 @@
     <addaction name="action_Restart"/>
     <addaction name="separator"/>
     <addaction name="action_Configure"/>
+    <addaction name="action_Configure_Tas"/>
     <addaction name="action_Configure_Current_Game"/>
    </widget>
    <widget class="QMenu" name="menu_View">
@@ -294,6 +295,11 @@
     <string>&amp;Capture Screenshot</string>
    </property>
   </action>
+  <action name="action_Configure_Tas">
+   <property name="text">
+    <string>Configure &amp;TAS...</string>
+   </property>
+  </action>
   <action name="action_Configure_Current_Game">
    <property name="enabled">
     <bool>false</bool>