summaryrefslogtreecommitdiff
path: root/src/yuzu
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu')
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/bootmanager.cpp2
-rw-r--r--src/yuzu/configuration/config.cpp28
-rw-r--r--src/yuzu/configuration/config.h2
-rw-r--r--src/yuzu/configuration/configure_audio.cpp9
-rw-r--r--src/yuzu/configuration/configure_audio.h2
-rw-r--r--src/yuzu/configuration/configure_audio.ui10
-rw-r--r--src/yuzu/configuration/configure_debug.ui181
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp30
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp19
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.h3
-rw-r--r--src/yuzu/configuration/configure_tas.cpp84
-rw-r--r--src/yuzu/configuration/configure_tas.h38
-rw-r--r--src/yuzu/configuration/configure_tas.ui153
-rw-r--r--src/yuzu/configuration/configure_vibration.cpp2
-rw-r--r--src/yuzu/debugger/controller.cpp18
-rw-r--r--src/yuzu/debugger/controller.h22
-rw-r--r--src/yuzu/main.cpp67
-rw-r--r--src/yuzu/main.h3
-rw-r--r--src/yuzu/main.ui6
20 files changed, 525 insertions, 157 deletions
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 19ba0dbba..b6dda283d 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/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
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 2e0ade815..1519a46ed 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -36,6 +36,7 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/mouse/mouse_input.h"
+#include "input_common/tas/tas_input.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
@@ -312,6 +313,7 @@ GRenderWindow::~GRenderWindow() {
}
void GRenderWindow::OnFrameDisplayed() {
+ input_subsystem->GetTas()->UpdateThread();
if (!first_frame) {
first_frame = true;
emit FirstFrameDisplayed();
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 1e1756c8a..b5796a8fc 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -221,7 +221,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off
-const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{
+const std::array<UISettings::Shortcut, 21> Config::default_hotkeys{{
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@@ -235,6 +235,9 @@ const std::array<UISettings::Shortcut, 18> Config::default_hotkeys{{
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
+ {QStringLiteral("TAS Start/Stop"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F5"), Qt::ApplicationShortcut}},
+ {QStringLiteral("TAS Reset"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F6"), Qt::ApplicationShortcut}},
+ {QStringLiteral("TAS Record"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F7"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Framerate Limit"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+U"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Mouse Panning"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F9"), Qt::ApplicationShortcut}},
@@ -542,7 +545,6 @@ void Config::ReadAudioValues() {
ReadBasicSetting(Settings::values.audio_device_id);
ReadBasicSetting(Settings::values.sink_id);
}
- ReadGlobalSetting(Settings::values.enable_audio_stretching);
ReadGlobalSetting(Settings::values.volume);
qt_config->endGroup();
@@ -569,6 +571,11 @@ void Config::ReadControlValues() {
Settings::values.mouse_panning = false;
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
+ ReadBasicSetting(Settings::values.tas_enable);
+ ReadBasicSetting(Settings::values.tas_loop);
+ ReadBasicSetting(Settings::values.tas_swap_controllers);
+ ReadBasicSetting(Settings::values.pause_tas_on_load);
+
ReadGlobalSetting(Settings::values.use_docked_mode);
// Disable docked mode if handheld is selected
@@ -666,6 +673,13 @@ void Config::ReadDataStorageValues() {
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)))
.toString()
.toStdString());
+ FS::SetYuzuPath(FS::YuzuPath::TASDir,
+ qt_config
+ ->value(QStringLiteral("tas_directory"),
+ QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)))
+ .toString()
+ .toStdString());
+
ReadBasicSetting(Settings::values.gamecard_inserted);
ReadBasicSetting(Settings::values.gamecard_current_game);
ReadBasicSetting(Settings::values.gamecard_path);
@@ -1168,7 +1182,6 @@ void Config::SaveAudioValues() {
WriteBasicSetting(Settings::values.sink_id);
WriteBasicSetting(Settings::values.audio_device_id);
}
- WriteGlobalSetting(Settings::values.enable_audio_stretching);
WriteGlobalSetting(Settings::values.volume);
qt_config->endGroup();
@@ -1194,6 +1207,11 @@ void Config::SaveControlValues() {
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
+ WriteBasicSetting(Settings::values.tas_enable);
+ WriteBasicSetting(Settings::values.tas_loop);
+ WriteBasicSetting(Settings::values.tas_swap_controllers);
+ WriteBasicSetting(Settings::values.pause_tas_on_load);
+
qt_config->endGroup();
}
@@ -1221,6 +1239,10 @@ void Config::SaveDataStorageValues() {
WriteSetting(QStringLiteral("dump_directory"),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)),
QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::DumpDir)));
+ WriteSetting(QStringLiteral("tas_directory"),
+ QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)),
+ QString::fromStdString(FS::GetYuzuPathString(FS::YuzuPath::TASDir)));
+
WriteBasicSetting(Settings::values.gamecard_inserted);
WriteBasicSetting(Settings::values.gamecard_current_game);
WriteBasicSetting(Settings::values.gamecard_path);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index 4733227b6..3ee694e7c 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,7 +42,7 @@ public:
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
- static const std::array<UISettings::Shortcut, 18> default_hotkeys;
+ static const std::array<UISettings::Shortcut, 21> default_hotkeys;
private:
void Initialize(const std::string& config_name);
diff --git a/src/yuzu/configuration/configure_audio.cpp b/src/yuzu/configuration/configure_audio.cpp
index 1d84bf4ed..f437cb53d 100644
--- a/src/yuzu/configuration/configure_audio.cpp
+++ b/src/yuzu/configuration/configure_audio.cpp
@@ -50,8 +50,6 @@ void ConfigureAudio::SetConfiguration() {
const auto volume_value = static_cast<int>(Settings::values.volume.GetValue());
ui->volume_slider->setValue(volume_value);
- ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue());
-
if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.volume.UsingGlobal()) {
ui->volume_combo_box->setCurrentIndex(0);
@@ -100,8 +98,6 @@ void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
}
void ConfigureAudio::ApplyConfiguration() {
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching,
- ui->toggle_audio_stretching, enable_audio_stretching);
if (Settings::IsConfiguringGlobal()) {
Settings::values.sink_id =
@@ -162,15 +158,10 @@ void ConfigureAudio::RetranslateUI() {
void ConfigureAudio::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
- ui->toggle_audio_stretching->setEnabled(
- Settings::values.enable_audio_stretching.UsingGlobal());
return;
}
- ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching,
- Settings::values.enable_audio_stretching,
- enable_audio_stretching);
connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->volume_slider->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
diff --git a/src/yuzu/configuration/configure_audio.h b/src/yuzu/configuration/configure_audio.h
index 9dbd3d93e..5a01c8de7 100644
--- a/src/yuzu/configuration/configure_audio.h
+++ b/src/yuzu/configuration/configure_audio.h
@@ -41,6 +41,4 @@ private:
void SetupPerGameUI();
std::unique_ptr<Ui::ConfigureAudio> ui;
-
- ConfigurationShared::CheckState enable_audio_stretching;
};
diff --git a/src/yuzu/configuration/configure_audio.ui b/src/yuzu/configuration/configure_audio.ui
index 9bd0cca96..bf736fc2c 100644
--- a/src/yuzu/configuration/configure_audio.ui
+++ b/src/yuzu/configuration/configure_audio.ui
@@ -32,16 +32,6 @@
</layout>
</item>
<item>
- <widget class="QCheckBox" name="toggle_audio_stretching">
- <property name="toolTip">
- <string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
- </property>
- <property name="text">
- <string>Enable audio stretching</string>
- </property>
- </widget>
- </item>
- <item>
<layout class="QHBoxLayout" name="_2">
<item>
<widget class="QLabel" name="audio_device_label">
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 3fe9ff7de..b884a56b0 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -2,85 +2,55 @@
<ui version="4.0">
<class>ConfigureDebug</class>
<widget class="QWidget" name="ConfigureDebug">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>400</width>
- <height>777</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Form</string>
- </property>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Logging</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_4">
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="label_1">
- <property name="text">
- <string>Global Log Filter</string>
+ <layout class="QGridLayout" name="gridLayout_1">
+ <item row="0" column="0" colspan="2">
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <item>
+ <widget class="QLabel" name="label_1">
+ <property name="text">
+ <string>Global Log Filter</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="log_filter_edit"/>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QCheckBox" name="toggle_console">
+ <property name="text">
+ <string>Show Log in Console</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QPushButton" name="open_log_button">
+ <property name="text">
+ <string>Open Log Location</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QCheckBox" name="extended_logging">
+ <property name="enabled">
+ <bool>true</bool>
</property>
- </widget>
- </item>
- <item>
- <widget class="QLineEdit" name="log_filter_edit"/>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
- <item>
- <widget class="QCheckBox" name="toggle_console">
- <property name="text">
- <string>Show Log in Console</string>
+ <property name="toolTip">
+ <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
</property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="open_log_button">
<property name="text">
- <string>Open Log Location</string>
+ <string>Enable Extended Logging**</string>
</property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QCheckBox" name="extended_logging">
- <property name="enabled">
- <bool>true</bool>
- </property>
- <property name="toolTip">
- <string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
- </property>
- <property name="text">
- <string>Enable Extended Logging</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_2">
- <property name="font">
- <font>
- <italic>true</italic>
- </font>
- </property>
- <property name="text">
- <string>This will be reset automatically when yuzu closes.</string>
- </property>
- <property name="indent">
- <number>20</number>
- </property>
- </widget>
- </item>
- </layout>
+ </widget>
+ </item>
+ </layout>
</widget>
</item>
<item>
@@ -111,7 +81,7 @@
<property name="title">
<string>Graphics</string>
</property>
- <layout class="QGridLayout" name="gridLayout_3">
+ <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QCheckBox" name="enable_graphics_debugging">
<property name="enabled">
@@ -176,33 +146,18 @@
<property name="title">
<string>Debugging</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_7">
- <item>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
<widget class="QCheckBox" name="fs_access_log">
<property name="text">
<string>Enable FS Access Log</string>
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<widget class="QCheckBox" name="reporting_services">
<property name="text">
- <string>Enable Verbose Reporting Services</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_4">
- <property name="font">
- <font>
- <italic>true</italic>
- </font>
- </property>
- <property name="text">
- <string>This will be reset automatically when yuzu closes.</string>
- </property>
- <property name="indent">
- <number>20</number>
+ <string>Enable Verbose Reporting Services**</string>
</property>
</widget>
</item>
@@ -214,47 +169,32 @@
<property name="title">
<string>Advanced</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_8">
- <item>
+ <layout class="QGridLayout" name="gridLayout_4">
+ <item> row="0" column="0">
<widget class="QCheckBox" name="quest_flag">
<property name="text">
<string>Kiosk (Quest) Mode</string>
</property>
</widget>
</item>
- <item>
+ <item row="1" column="0">
<widget class="QCheckBox" name="enable_cpu_debugging">
<property name="text">
<string>Enable CPU Debugging</string>
</property>
</widget>
</item>
- <item>
+ <item row="2" column="0">
<widget class="QCheckBox" name="use_debug_asserts">
<property name="text">
<string>Enable Debug Asserts</string>
</property>
</widget>
</item>
- <item>
+ <item row="0" column="1">
<widget class="QCheckBox" name="use_auto_stub">
<property name="text">
- <string>Enable Auto-Stub</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="label_5">
- <property name="font">
- <font>
- <italic>true</italic>
- </font>
- </property>
- <property name="text">
- <string>This will be reset automatically when yuzu closes.</string>
- </property>
- <property name="indent">
- <number>20</number>
+ <string>Enable Auto-Stub**</string>
</property>
</widget>
</item>
@@ -262,20 +202,19 @@
</widget>
</item>
<item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
+ <widget class="QLabel" name="label_5">
+ <property name="font">
+ <font>
+ <italic>true</italic>
+ </font>
</property>
- <property name="sizeType">
- <enum>QSizePolicy::Expanding</enum>
+ <property name="text">
+ <string>**This will be reset automatically when yuzu closes.</string>
</property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
+ <property name="indent">
+ <number>20</number>
</property>
- </spacer>
+ </widget>
</item>
</layout>
</widget>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 7527c068b..88f4bf388 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -124,6 +124,19 @@ QString ButtonToText(const Common::ParamPackage& param) {
return GetKeyName(param.Get("code", 0));
}
+ if (param.Get("engine", "") == "tas") {
+ if (param.Has("axis")) {
+ const QString axis_str = QString::fromStdString(param.Get("axis", ""));
+
+ return QObject::tr("TAS Axis %1").arg(axis_str);
+ }
+ if (param.Has("button")) {
+ const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
+ return QObject::tr("TAS Btn %1").arg(button_str);
+ }
+ return GetKeyName(param.Get("code", 0));
+ }
+
if (param.Get("engine", "") == "cemuhookudp") {
if (param.Has("pad_index")) {
const QString motion_str = QString::fromStdString(param.Get("pad_index", ""));
@@ -187,7 +200,8 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir)
const QString axis_y_str = QString::fromStdString(param.Get("axis_y", ""));
const bool invert_x = param.Get("invert_x", "+") == "-";
const bool invert_y = param.Get("invert_y", "+") == "-";
- if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse") {
+ if (engine_str == "sdl" || engine_str == "gcpad" || engine_str == "mouse" ||
+ engine_str == "tas") {
if (dir == "modifier") {
return QObject::tr("[unused]");
}
@@ -926,9 +940,9 @@ void ConfigureInputPlayer::UpdateUI() {
int slider_value;
auto& param = analogs_param[analog_id];
- const bool is_controller = param.Get("engine", "") == "sdl" ||
- param.Get("engine", "") == "gcpad" ||
- param.Get("engine", "") == "mouse";
+ const bool is_controller =
+ param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad" ||
+ param.Get("engine", "") == "mouse" || param.Get("engine", "") == "tas";
if (is_controller) {
if (!param.Has("deadzone")) {
@@ -1045,8 +1059,12 @@ int ConfigureInputPlayer::GetIndexFromControllerType(Settings::ControllerType ty
void ConfigureInputPlayer::UpdateInputDevices() {
input_devices = input_subsystem->GetInputDevices();
ui->comboDevices->clear();
- for (auto device : input_devices) {
- ui->comboDevices->addItem(QString::fromStdString(device.Get("display", "Unknown")), {});
+ for (auto& device : input_devices) {
+ const std::string display = device.Get("display", "Unknown");
+ ui->comboDevices->addItem(QString::fromStdString(display), {});
+ if (display == "TAS") {
+ device.Set("pad", static_cast<u8>(player_index));
+ }
}
}
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index 9c890ed5d..da328d904 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -175,7 +175,7 @@ void PlayerControlPreview::ResetInputs() {
}
void PlayerControlPreview::UpdateInput() {
- if (!is_enabled && !mapping_active) {
+ if (!is_enabled && !mapping_active && !Settings::values.tas_enable) {
return;
}
bool input_changed = false;
@@ -222,6 +222,19 @@ void PlayerControlPreview::UpdateInput() {
if (input_changed) {
update();
+ if (controller_callback.input != nullptr) {
+ ControllerInput input{
+ .axis_values = {std::pair<float, float>{
+ axis_values[Settings::NativeAnalog::LStick].value.x(),
+ axis_values[Settings::NativeAnalog::LStick].value.y()},
+ std::pair<float, float>{
+ axis_values[Settings::NativeAnalog::RStick].value.x(),
+ axis_values[Settings::NativeAnalog::RStick].value.y()}},
+ .button_values = button_values,
+ .changed = true,
+ };
+ controller_callback.input(std::move(input));
+ }
}
if (mapping_active) {
@@ -229,6 +242,10 @@ void PlayerControlPreview::UpdateInput() {
}
}
+void PlayerControlPreview::SetCallBack(ControllerCallback callback_) {
+ controller_callback = std::move(callback_);
+}
+
void PlayerControlPreview::paintEvent(QPaintEvent* event) {
QFrame::paintEvent(event);
QPainter p(this);
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
index f4a6a5e1b..f4bbfa528 100644
--- a/src/yuzu/configuration/configure_input_player_widget.h
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -9,6 +9,7 @@
#include <QPointer>
#include "common/settings.h"
#include "core/frontend/input.h"
+#include "yuzu/debugger/controller.h"
class QLabel;
@@ -33,6 +34,7 @@ public:
void BeginMappingAnalog(std::size_t button_id);
void EndMapping();
void UpdateInput();
+ void SetCallBack(ControllerCallback callback_);
protected:
void paintEvent(QPaintEvent* event) override;
@@ -181,6 +183,7 @@ private:
using StickArray =
std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>;
+ ControllerCallback controller_callback;
bool is_enabled{};
bool mapping_active{};
int blink_counter{};
diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp
new file mode 100644
index 000000000..b666b175a
--- /dev/null
+++ b/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.GetValue());
+ ui->tas_control_swap->setChecked(Settings::values.tas_swap_controllers.GetValue());
+ ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue());
+ ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue());
+}
+
+void ConfigureTasDialog::ApplyConfiguration() {
+ Common::FS::SetYuzuPath(Common::FS::YuzuPath::TASDir, ui->tas_path_edit->text().toStdString());
+ Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked());
+ Settings::values.tas_swap_controllers.SetValue(ui->tas_control_swap->isChecked());
+ Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked());
+ Settings::values.pause_tas_on_load.SetValue(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.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();
+}
diff --git a/src/yuzu/configuration/configure_tas.h b/src/yuzu/configuration/configure_tas.h
new file mode 100644
index 000000000..1546bf16f
--- /dev/null
+++ b/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;
+};
diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui
new file mode 100644
index 000000000..95575ed9d
--- /dev/null
+++ b/src/yuzu/configuration/configure_tas.ui
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ConfigureTas</class>
+ <widget class="QDialog" name="ConfigureTas">
+ <layout class="QVBoxLayout" name="verticalLayout_1">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_1">
+ <item>
+ <widget class="QGroupBox" name="groupBox_1">
+ <property name="title">
+ <string>TAS</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_1">
+ <item row="0" column="0" colspan="4">
+ <widget class="QLabel" name="label_1">
+ <property name="text">
+ <string>Reads controller input from scripts in the same format as TAS-nx scripts.&lt;br/&gt;For a more detailed explanation please consult the FAQ on the yuzu website.</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="4">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>To check which hotkeys control the playback/recording, please refer to the Hotkey settings (General -> Hotkeys).</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" colspan="4">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>WARNING: This is an experimental feature.&lt;br/&gt;It will not play back scripts frame perfectly with the current, imperfect syncing method.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Settings</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <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="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_3">
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Script Directory</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <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>
+ </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>
diff --git a/src/yuzu/configuration/configure_vibration.cpp b/src/yuzu/configuration/configure_vibration.cpp
index 9d92c4949..46a0f3025 100644
--- a/src/yuzu/configuration/configure_vibration.cpp
+++ b/src/yuzu/configuration/configure_vibration.cpp
@@ -99,7 +99,7 @@ void ConfigureVibration::SetVibrationDevices(std::size_t player_index) {
const auto guid = param.Get("guid", "");
const auto port = param.Get("port", "");
- if (engine.empty() || engine == "keyboard" || engine == "mouse") {
+ if (engine.empty() || engine == "keyboard" || engine == "mouse" || engine == "tas") {
continue;
}
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp
index c1fc69578..5a844409b 100644
--- a/src/yuzu/debugger/controller.cpp
+++ b/src/yuzu/debugger/controller.cpp
@@ -6,10 +6,13 @@
#include <QLayout>
#include <QString>
#include "common/settings.h"
+#include "input_common/main.h"
+#include "input_common/tas/tas_input.h"
#include "yuzu/configuration/configure_input_player_widget.h"
#include "yuzu/debugger/controller.h"
-ControllerDialog::ControllerDialog(QWidget* parent) : QWidget(parent, Qt::Dialog) {
+ControllerDialog::ControllerDialog(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_)
+ : QWidget(parent, Qt::Dialog), input_subsystem{input_subsystem_} {
setObjectName(QStringLiteral("Controller"));
setWindowTitle(tr("Controller P1"));
resize(500, 350);
@@ -38,6 +41,9 @@ void ControllerDialog::refreshConfiguration() {
constexpr std::size_t player = 0;
widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs);
widget->SetControllerType(players[player].controller_type);
+ ControllerCallback callback{[this](ControllerInput input) { InputController(input); }};
+ widget->SetCallBack(callback);
+ widget->repaint();
widget->SetConnectedStatus(players[player].connected);
}
@@ -67,3 +73,13 @@ void ControllerDialog::hideEvent(QHideEvent* ev) {
widget->SetConnectedStatus(false);
QWidget::hideEvent(ev);
}
+
+void ControllerDialog::InputController(ControllerInput input) {
+ u32 buttons = 0;
+ int index = 0;
+ for (bool btn : input.button_values) {
+ buttons |= (btn ? 1U : 0U) << index;
+ index++;
+ }
+ input_subsystem->GetTas()->RecordInput(buttons, input.axis_values);
+}
diff --git a/src/yuzu/debugger/controller.h b/src/yuzu/debugger/controller.h
index c54750070..7742db58b 100644
--- a/src/yuzu/debugger/controller.h
+++ b/src/yuzu/debugger/controller.h
@@ -4,18 +4,35 @@
#pragma once
+#include <QFileSystemWatcher>
#include <QWidget>
+#include "common/settings.h"
class QAction;
class QHideEvent;
class QShowEvent;
class PlayerControlPreview;
+namespace InputCommon {
+class InputSubsystem;
+}
+
+struct ControllerInput {
+ std::array<std::pair<float, float>, Settings::NativeAnalog::NUM_STICKS_HID> axis_values{};
+ std::array<bool, Settings::NativeButton::NumButtons> button_values{};
+ bool changed{};
+};
+
+struct ControllerCallback {
+ std::function<void(ControllerInput)> input;
+};
+
class ControllerDialog : public QWidget {
Q_OBJECT
public:
- explicit ControllerDialog(QWidget* parent = nullptr);
+ explicit ControllerDialog(QWidget* parent = nullptr,
+ InputCommon::InputSubsystem* input_subsystem_ = nullptr);
/// Returns a QAction that can be used to toggle visibility of this dialog.
QAction* toggleViewAction();
@@ -26,6 +43,9 @@ protected:
void hideEvent(QHideEvent* ev) override;
private:
+ void InputController(ControllerInput input);
QAction* toggle_view_action = nullptr;
+ QFileSystemWatcher* watcher = nullptr;
PlayerControlPreview* widget;
+ InputCommon::InputSubsystem* input_subsystem;
};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index f4e49001d..3c2824362 100644
--- a/src/yuzu/main.cpp
+++ b/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"
@@ -102,6 +103,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
#include "input_common/main.h"
+#include "input_common/tas/tas_input.h"
#include "util/overlay_dialog.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
@@ -747,6 +749,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"));
@@ -841,7 +848,7 @@ void GMainWindow::InitializeDebugWidgets() {
waitTreeWidget->hide();
debug_menu->addAction(waitTreeWidget->toggleViewAction());
- controller_dialog = new ControllerDialog(this);
+ controller_dialog = new ControllerDialog(this, input_subsystem.get());
controller_dialog->hide();
debug_menu->addAction(controller_dialog->toggleViewAction());
@@ -1014,6 +1021,28 @@ void GMainWindow::InitializeHotkeys() {
render_window->setAttribute(Qt::WA_Hover, true);
}
});
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this),
+ &QShortcut::activated, this, [&] {
+ if (!emulation_running) {
+ return;
+ }
+ input_subsystem->GetTas()->StartStop();
+ });
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this),
+ &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); });
+ connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this),
+ &QShortcut::activated, this, [&] {
+ if (!emulation_running) {
+ return;
+ }
+ bool is_recording = input_subsystem->GetTas()->Record();
+ if (!is_recording) {
+ const auto res = QMessageBox::question(this, tr("TAS Recording"),
+ tr("Overwrite file of player 1?"),
+ QMessageBox::Yes | QMessageBox::No);
+ input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
+ }
+ });
}
void GMainWindow::SetDefaultUIGeometry() {
@@ -1132,6 +1161,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);
@@ -1464,6 +1494,8 @@ void GMainWindow::ShutdownGame() {
game_list->show();
}
game_list->SetFilterFocus();
+ tas_label->clear();
+ input_subsystem->GetTas()->Stop();
render_window->removeEventFilter(render_window);
render_window->setAttribute(Qt::WA_Hover, false);
@@ -2698,6 +2730,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());
@@ -2874,12 +2919,32 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie
}
}
+QString GMainWindow::GetTasStateDescription() const {
+ auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus();
+ switch (tas_status) {
+ case TasInput::TasState::Running:
+ return tr("TAS state: Running %1/%2").arg(current_tas_frame).arg(total_tas_frames);
+ case TasInput::TasState::Recording:
+ return tr("TAS state: Recording %1").arg(total_tas_frames);
+ case TasInput::TasState::Stopped:
+ return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames);
+ default:
+ return tr("TAS State: Invalid");
+ }
+}
+
void GMainWindow::UpdateStatusBar() {
if (emu_thread == nullptr) {
status_bar_update_timer.stop();
return;
}
+ if (Settings::values.tas_enable) {
+ tas_label->setText(GetTasStateDescription());
+ } else {
+ tas_label->clear();
+ }
+
auto& system = Core::System::GetInstance();
auto results = system.GetAndResetPerfStats();
auto& shader_notify = system.GPU().ShaderNotify();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 38e66ccd0..36eed6103 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -259,6 +259,7 @@ private slots:
void OnMenuInstallToNAND();
void OnMenuRecentFile();
void OnConfigure();
+ void OnConfigureTas();
void OnConfigurePerGame();
void OnLoadAmiibo();
void OnOpenYuzuFolder();
@@ -300,6 +301,7 @@ private:
void OpenURL(const QUrl& url);
void LoadTranslation();
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
+ QString GetTasStateDescription() const;
Ui::MainWindow ui;
@@ -318,6 +320,7 @@ private:
QLabel* emu_speed_label = nullptr;
QLabel* game_fps_label = nullptr;
QLabel* emu_frametime_label = nullptr;
+ QLabel* tas_label = nullptr;
QPushButton* gpu_accuracy_button = nullptr;
QPushButton* renderer_status_button = nullptr;
QPushButton* dock_status_button = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index 048870687..653c010d8 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -100,6 +100,7 @@
<addaction name="action_Rederive"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
+ <addaction name="action_Configure_Tas"/>
</widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
@@ -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>