diff --git a/rpcs3/Emu/IdManager.h b/rpcs3/Emu/IdManager.h index 7e536b9265..94d2d3ffd5 100644 --- a/rpcs3/Emu/IdManager.h +++ b/rpcs3/Emu/IdManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Utilities/types.h" #include "Utilities/mutex.h" @@ -633,8 +633,8 @@ public: } // Emplace the object returned by provider() and return it if no object exists - template - static auto import(F&& provider) -> decltype(static_cast>(provider())) + template + static auto import(F&& provider, Args&&... args) -> decltype(static_cast>(provider(std::forward(args)...))) { std::shared_ptr ptr; { @@ -644,7 +644,7 @@ public: if (!cur) { - ptr = provider(); + ptr = provider(std::forward(args)...); if (ptr) { @@ -662,8 +662,8 @@ public: } // Emplace the object return by provider() (old object will be removed if it exists) - template - static auto import_always(F&& provider) -> decltype(static_cast>(provider())) + template + static auto import_always(F&& provider, Args&&... args) -> decltype(static_cast>(provider(std::forward(args)...))) { std::shared_ptr ptr; std::shared_ptr old; @@ -672,7 +672,7 @@ public: auto& cur = g_vec[get_type()]; - ptr = provider(); + ptr = provider(std::forward(args)...); if (ptr) { diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index 151aa54884..b29d1195e5 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "PadHandler.h" +#include "pad_thread.h" cfg_input g_cfg_input; @@ -268,13 +269,25 @@ bool PadHandlerBase::has_led() return b_has_led; } -std::string PadHandlerBase::get_config_dir(pad_handler type) +std::string PadHandlerBase::get_config_dir(pad_handler type, const std::string& title_id) { + if (!title_id.empty()) + { + return Emu.GetCustomInputConfigDir(title_id) + fmt::format("%s", type) + "/"; + } return fs::get_config_dir() + "/InputConfigs/" + fmt::format("%s", type) + "/"; } -std::string PadHandlerBase::get_config_filename(int i) +std::string PadHandlerBase::get_config_filename(int i, const std::string& title_id) { + if (!title_id.empty() && fs::is_file(Emu.GetCustomInputConfigPath(title_id))) + { + const std::string path = Emu.GetCustomInputConfigDir(title_id) + g_cfg_input.player[i]->handler.to_string() + "/" + g_cfg_input.player[i]->profile.to_string() + ".yml"; + if (fs::is_file(path)) + { + return path; + } + } return fs::get_config_dir() + "/InputConfigs/" + g_cfg_input.player[i]->handler.to_string() + "/" + g_cfg_input.player[i]->profile.to_string() + ".yml"; } @@ -286,7 +299,7 @@ void PadHandlerBase::init_configs() { if (g_cfg_input.player[i]->handler == m_type) { - init_config(&m_pad_configs[index], get_config_filename(i)); + init_config(&m_pad_configs[index], get_config_filename(i, pad::g_title_id)); index++; } } diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index 414f2b142f..a1d57aa8ca 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -7,6 +7,7 @@ #include "Utilities/Config.h" #include "Utilities/types.h" #include "Emu/System.h" +#include "Emu/GameInfo.h" // TODO: HLE info (constants, structs, etc.) should not be available here @@ -268,7 +269,7 @@ struct cfg_player final : cfg::node struct cfg_input final : cfg::node { - const std::string cfg_name = fs::get_config_dir() + "/config_input.yml"; + std::string cfg_name = fs::get_config_dir() + "/config_input.yml"; cfg_player player1{ this, "Player 1 Input", pad_handler::keyboard }; cfg_player player2{ this, "Player 2 Input", pad_handler::null }; @@ -280,18 +281,32 @@ struct cfg_input final : cfg::node cfg_player *player[7]{ &player1, &player2, &player3, &player4, &player5, &player6, &player7 }; // Thanks gcc! - bool load() + bool load(const std::string& title_id = "") { - if (fs::file cfg_file{ cfg_name, fs::read }) + cfg_name = Emu.GetCustomInputConfigPath(title_id); + + if (!fs::is_file(cfg_name)) + { + cfg_name = fs::get_config_dir() + "/config_input.yml"; + } + + if (fs::file cfg_file{cfg_name, fs::read}) { return from_string(cfg_file.to_string()); } - return false; } - void save() + void save(const std::string& title_id = "") { + if (title_id.empty()) + { + cfg_name = fs::get_config_dir() + "/config_input.yml"; + } + else + { + cfg_name = Emu.GetCustomInputConfigPath(title_id); + } fs::file(cfg_name, fs::rewrite).write(to_string()); } }; @@ -460,8 +475,8 @@ public: bool has_deadzones(); bool has_led(); - static std::string get_config_dir(pad_handler type); - static std::string get_config_filename(int i); + static std::string get_config_dir(pad_handler type, const std::string& title_id = ""); + static std::string get_config_filename(int i, const std::string& title_id = ""); virtual bool Init() { return true; } PadHandlerBase(pad_handler type = pad_handler::null); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index c09e35f49b..638e243540 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -39,7 +39,6 @@ #include "Utilities/GDBDebugServer.h" -#include "Utilities/sysinfo.h" #include "Utilities/JIT.h" #if defined(_WIN32) || defined(HAVE_VULKAN) @@ -303,31 +302,31 @@ void Emulator::Init() if (g_cfg.vfs.init_dirs) { - fs::create_path(dev_hdd0); - fs::create_path(dev_hdd1); - fs::create_path(dev_usb); - fs::create_dir(dev_hdd0 + "game/"); - fs::create_dir(dev_hdd0 + "game/TEST12345/"); - fs::create_dir(dev_hdd0 + "game/TEST12345/USRDIR/"); - fs::create_dir(dev_hdd0 + "game/.locks/"); - fs::create_dir(dev_hdd0 + "home/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/exdata/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/savedata/"); - fs::create_dir(dev_hdd0 + "home/" + m_usr + "/trophy/"); - fs::write_file(dev_hdd0 + "home/" + m_usr + "/localusername", fs::create + fs::excl + fs::write, "User"s); - fs::create_dir(dev_hdd0 + "disc/"); - fs::create_dir(dev_hdd0 + "savedata/"); - fs::create_dir(dev_hdd0 + "savedata/vmc/"); - fs::create_dir(dev_hdd1 + "cache/"); - fs::create_dir(dev_hdd1 + "game/"); + fs::create_path(dev_hdd0); + fs::create_path(dev_hdd1); + fs::create_path(dev_usb); + fs::create_dir(dev_hdd0 + "game/"); + fs::create_dir(dev_hdd0 + "game/TEST12345/"); + fs::create_dir(dev_hdd0 + "game/TEST12345/USRDIR/"); + fs::create_dir(dev_hdd0 + "game/.locks/"); + fs::create_dir(dev_hdd0 + "home/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/exdata/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/savedata/"); + fs::create_dir(dev_hdd0 + "home/" + m_usr + "/trophy/"); + fs::write_file(dev_hdd0 + "home/" + m_usr + "/localusername", fs::create + fs::excl + fs::write, "User"s); + fs::create_dir(dev_hdd0 + "disc/"); + fs::create_dir(dev_hdd0 + "savedata/"); + fs::create_dir(dev_hdd0 + "savedata/vmc/"); + fs::create_dir(dev_hdd1 + "cache/"); + fs::create_dir(dev_hdd1 + "game/"); } fs::create_path(fs::get_cache_dir() + "shaderlog/"); fs::create_path(fs::get_config_dir() + "captures/"); #ifdef WITH_GDB_DEBUGGER - LOG_SUCCESS(GENERAL, "GDB debug server will be started and listening on %d upon emulator boot", (int) g_cfg.misc.gdb_server_port); + LOG_SUCCESS(GENERAL, "GDB debug server will be started and listening on %d upon emulator boot", (int)g_cfg.misc.gdb_server_port); #endif // Initialize patch engine @@ -506,7 +505,7 @@ bool Emulator::BootRsxCapture(const std::string& path) GetCallbacks().on_ready(); auto gsrender = fxm::import(Emu.GetCallbacks().get_gs_render); - auto padhandler = fxm::import(Emu.GetCallbacks().get_pad_handler); + auto padhandler = fxm::import(Emu.GetCallbacks().get_pad_handler, ""); if (gsrender.get() == nullptr || padhandler.get() == nullptr) return false; @@ -727,6 +726,22 @@ std::string Emulator::GetCustomConfigPath(const std::string& title_id, bool get_ return path; } +std::string Emulator::GetCustomInputConfigDir(const std::string& title_id) +{ + // Notice: the extra folder for each title id may be removed at a later stage + // Warning: make sure to change any function that removes this directory as well +#ifdef _WIN32 + return fs::get_config_dir() + "config/custom_input_configs/" + title_id + "/"; +#else + return fs::get_config_dir() + "custom_input_configs/" + title_id + "/"; +#endif +} + +std::string Emulator::GetCustomInputConfigPath(const std::string& title_id) +{ + return GetCustomInputConfigDir(title_id) + "/config_input_" + title_id + ".yml"; +} + void Emulator::SetForceBoot(bool force_boot) { m_force_boot = force_boot; @@ -1343,7 +1358,7 @@ void Emulator::Load(bool add_only, bool force_global_config) } fxm::import(Emu.GetCallbacks().get_gs_render); // TODO: must be created in appropriate sys_rsx syscall - fxm::import(Emu.GetCallbacks().get_pad_handler); + fxm::import(Emu.GetCallbacks().get_pad_handler, m_title_id); network_thread_init(); } else if (ppu_prx.open(elf_file) == elf_error::ok) diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 99d1916014..40a5d2236d 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -189,12 +189,12 @@ struct EmuCallbacks std::function on_stop; std::function on_ready; std::function exit; - std::function reset_pads; + std::function reset_pads; std::function enable_pads; std::function handle_taskbar_progress; // (type, value) type: 0 for reset, 1 for increment, 2 for set_limit std::function()> get_kb_handler; std::function()> get_mouse_handler; - std::function()> get_pad_handler; + std::function(const std::string&)> get_pad_handler; std::function()> get_gs_frame; std::function()> get_gs_render; std::function()> get_audio; @@ -326,6 +326,8 @@ public: static std::string GetCustomConfigDir(); static std::string GetCustomConfigPath(const std::string& title_id, bool get_deprecated_path = false); + static std::string GetCustomInputConfigDir(const std::string& title_id); + static std::string GetCustomInputConfigPath(const std::string& title_id); void SetForceBoot(bool force_boot); diff --git a/rpcs3/Icons/combo_config_bordered.png b/rpcs3/Icons/combo_config_bordered.png new file mode 100644 index 0000000000..16a5e106cc Binary files /dev/null and b/rpcs3/Icons/combo_config_bordered.png differ diff --git a/rpcs3/Icons/controllers_2.png b/rpcs3/Icons/controllers_2.png new file mode 100644 index 0000000000..b26813ca35 Binary files /dev/null and b/rpcs3/Icons/controllers_2.png differ diff --git a/rpcs3/pad_thread.cpp b/rpcs3/pad_thread.cpp index cb4f81aab6..d3c22bc7a9 100644 --- a/rpcs3/pad_thread.cpp +++ b/rpcs3/pad_thread.cpp @@ -14,6 +14,7 @@ namespace pad { atomic_t g_current = nullptr; std::recursive_mutex g_pad_mutex; + std::string g_title_id; } struct pad_setting @@ -23,8 +24,9 @@ struct pad_setting u32 device_type; }; -pad_thread::pad_thread(void *_curthread, void *_curwindow) : curthread(_curthread), curwindow(_curwindow) +pad_thread::pad_thread(void *_curthread, void *_curwindow, const std::string& title_id) : curthread(_curthread), curwindow(_curwindow) { + pad::g_title_id = title_id; Init(); thread = std::make_shared(&pad_thread::ThreadFunc, this); @@ -65,7 +67,7 @@ void pad_thread::Init() handlers.clear(); - g_cfg_input.load(); + g_cfg_input.load(pad::g_title_id); std::shared_ptr keyptr; @@ -142,8 +144,9 @@ void pad_thread::SetRumble(const u32 pad, u8 largeMotor, bool smallMotor) } } -void pad_thread::Reset() +void pad_thread::Reset(const std::string& title_id) { + pad::g_title_id = title_id; reset = active.load(); } diff --git a/rpcs3/pad_thread.h b/rpcs3/pad_thread.h index e918366bd4..499555eaa0 100644 --- a/rpcs3/pad_thread.h +++ b/rpcs3/pad_thread.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include @@ -16,14 +16,14 @@ struct PadInfo class pad_thread { public: - pad_thread(void *_curthread, void *_curwindow); // void * instead of QThread * and QWindow * because of include in emucore + pad_thread(void* _curthread, void* _curwindow, const std::string& title_id = ""); // void * instead of QThread * and QWindow * because of include in emucore ~pad_thread(); PadInfo& GetInfo() { return m_info; } auto& GetPads() { return m_pads; } void SetRumble(const u32 pad, u8 largeMotor, bool smallMotor); void Init(); - void Reset(); + void Reset(const std::string& title_id = ""); void SetEnabled(bool enabled); void SetIntercepted(bool intercepted); @@ -50,6 +50,7 @@ namespace pad { extern atomic_t g_current; extern std::recursive_mutex g_pad_mutex; + extern std::string g_title_id; static inline class pad_thread* get_current_handler() { diff --git a/rpcs3/resources.qrc b/rpcs3/resources.qrc index 3574e9ded1..14a3a0d619 100644 --- a/rpcs3/resources.qrc +++ b/rpcs3/resources.qrc @@ -18,5 +18,7 @@ Icons/open.png Icons/custom_config.png Icons/custom_config_2.png + Icons/controllers_2.png + Icons/combo_config_bordered.png diff --git a/rpcs3/rpcs3_app.cpp b/rpcs3/rpcs3_app.cpp index 2a7abe4e5e..1c3445a07b 100644 --- a/rpcs3/rpcs3_app.cpp +++ b/rpcs3/rpcs3_app.cpp @@ -142,9 +142,9 @@ void rpcs3_app::InitializeCallbacks() RequestCallAfter(std::move(func)); }; - callbacks.reset_pads = [this]() + callbacks.reset_pads = [this](const std::string& title_id = "") { - pad::get_current_handler()->Reset(); + pad::get_current_handler()->Reset(title_id); }; callbacks.enable_pads = [this](bool enable) { @@ -183,9 +183,9 @@ void rpcs3_app::InitializeCallbacks() } }; - callbacks.get_pad_handler = [this]() -> std::shared_ptr + callbacks.get_pad_handler = [this](const std::string& title_id) -> std::shared_ptr { - return std::make_shared(thread(), gameWindow); + return std::make_shared(thread(), gameWindow, title_id); }; callbacks.get_gs_frame = [this]() -> std::unique_ptr diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 13aaa8a056..0fe493c2ac 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -1,6 +1,7 @@ #include "game_list_frame.h" #include "qt_utils.h" #include "settings_dialog.h" +#include "pad_settings_dialog.h" #include "table_item_delegate.h" #include "custom_table_widget_item.h" #include "input_dialog.h" @@ -114,6 +115,10 @@ game_list_frame::game_list_frame(std::shared_ptr guiSettings, std: QMenu* configure = new QMenu(this); configure->addActions(m_columnActs); configure->exec(mapToGlobal(pos)); + + QMenu* pad_configure = new QMenu(this); + pad_configure->addActions(m_columnActs); + pad_configure->exec(mapToGlobal(pos)); }); connect(m_xgrid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); @@ -471,12 +476,14 @@ void game_list_frame::Refresh(const bool fromDrive, const bool scrollAfter) } const auto compat = m_game_compat->GetCompatibility(game.serial); + const bool hasCustomConfig = fs::is_file(Emu.GetCustomConfigPath(game.serial)) || fs::is_file(Emu.GetCustomConfigPath(game.serial, true)); + const bool hasCustomPadConfig = fs::is_file(Emu.GetCustomInputConfigPath(game.serial)); const QColor color = getGridCompatibilityColor(compat.color); - const QPixmap pxmap = PaintedPixmap(img, hasCustomConfig, color); + const QPixmap pxmap = PaintedPixmap(img, hasCustomConfig, hasCustomPadConfig, color); - m_game_data.push_back(game_info(new gui_game_info{ game, compat, img, pxmap, hasCustomConfig })); + m_game_data.push_back(game_info(new gui_game_info{game, compat, img, pxmap, hasCustomConfig, hasCustomPadConfig})); } catch (const std::exception& e) { @@ -695,6 +702,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) } myMenu.addAction(boot); QAction* configure = myMenu.addAction(tr("&Configure")); + QAction* pad_configure = myMenu.addAction(tr("&Configure pads")); QAction* createPPUCache = myMenu.addAction(tr("&Create PPU Cache")); myMenu.addSeparator(); QAction* renameTitle = myMenu.addAction(tr("&Rename In Game List")); @@ -709,9 +717,20 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) QAction* remove_custom_config = remove_menu->addAction(tr("&Remove Custom Configuration")); connect(remove_custom_config, &QAction::triggered, [=]() { - if (RemoveCustomConfiguration(currGame.serial, true)) + if (RemoveCustomConfiguration(currGame.serial, gameinfo, true)) { - ShowCustomConfigIcon(item, false); + ShowCustomConfigIcon(item, config::type::emu); + } + }); + } + if (gameinfo->hasCustomPadConfig) + { + QAction* remove_custom_pad_config = remove_menu->addAction(tr("&Remove Custom Pad Configuration")); + connect(remove_custom_pad_config, &QAction::triggered, [=]() + { + if (RemoveCustomPadConfiguration(currGame.serial, gameinfo, true)) + { + ShowCustomConfigIcon(item, config::type::pad); } }); } @@ -775,7 +794,33 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) settings_dialog dlg(m_gui_settings, m_emu_settings, 0, this, &currGame); if (dlg.exec() == QDialog::Accepted && !gameinfo->hasCustomConfig) { - ShowCustomConfigIcon(item, true); + gameinfo->hasCustomConfig = true; + ShowCustomConfigIcon(item, config::type::emu); + } + }); + connect(pad_configure, &QAction::triggered, [=] + { + if (!Emu.IsStopped()) + { + Emu.GetCallbacks().enable_pads(false); + } + pad_settings_dialog dlg(this, &currGame); + connect(&dlg, &QDialog::finished, [this](int/* result*/) + { + if (Emu.IsStopped()) + { + return; + } + Emu.GetCallbacks().reset_pads(Emu.GetTitleID()); + }); + if (dlg.exec() == QDialog::Accepted && !gameinfo->hasCustomPadConfig) + { + gameinfo->hasCustomPadConfig = true; + ShowCustomConfigIcon(item, config::type::pad); + } + if (!Emu.IsStopped()) + { + Emu.GetCallbacks().enable_pads(true); } }); connect(hide_serial, &QAction::triggered, [=](bool checked) @@ -801,7 +846,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) } QMessageBox* mb = new QMessageBox(QMessageBox::Question, tr("Confirm %1 Removal").arg(qstr(currGame.category)), tr("Permanently remove %0 from drive?\nPath: %1").arg(name).arg(qstr(currGame.path)), QMessageBox::Yes | QMessageBox::No, this); - mb->setCheckBox(new QCheckBox(tr("Remove caches and custom config"))); + mb->setCheckBox(new QCheckBox(tr("Remove caches and custom configs"))); mb->deleteLater(); if (mb->exec() == QMessageBox::Yes) { @@ -811,6 +856,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) RemovePPUCache(cache_base_dir); RemoveSPUCache(cache_base_dir); RemoveCustomConfiguration(currGame.serial); + RemoveCustomPadConfiguration(currGame.serial); } fs::remove_all(currGame.path); m_game_data.erase(std::remove(m_game_data.begin(), m_game_data.end(), gameinfo), m_game_data.end()); @@ -933,7 +979,7 @@ bool game_list_frame::CreatePPUCache(const std::string& path) return success; } -bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, bool is_interactive) +bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, game_info game, bool is_interactive) { const std::string config_path_new = Emu.GetCustomConfigPath(title_id); const std::string config_path_old = Emu.GetCustomConfigPath(title_id, true); @@ -954,6 +1000,10 @@ bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, boo } if (fs::remove_file(path)) { + if (game) + { + game->hasCustomConfig = false; + } LOG_SUCCESS(GENERAL, "Removed configuration file: %s", path); } else @@ -971,6 +1021,41 @@ bool game_list_frame::RemoveCustomConfiguration(const std::string& title_id, boo return result; } +bool game_list_frame::RemoveCustomPadConfiguration(const std::string& title_id, game_info game, bool is_interactive) +{ + const std::string config_dir = Emu.GetCustomInputConfigDir(title_id); + + if (!fs::is_dir(config_dir)) + return true; + + if (is_interactive && QMessageBox::question(this, tr("Confirm Removal"), (!Emu.IsStopped() && Emu.GetTitleID() == title_id) + ? tr("Remove custom pad configuration?\nYour configuration will revert to the global pad settings.") + : tr("Remove custom pad configuration?")) != QMessageBox::Yes) + return true; + + if (QDir(qstr(config_dir)).removeRecursively()) + { + if (game) + { + game->hasCustomPadConfig = false; + } + if (!Emu.IsStopped() && Emu.GetTitleID() == title_id) + { + Emu.GetCallbacks().enable_pads(false); + Emu.GetCallbacks().reset_pads(title_id); + Emu.GetCallbacks().enable_pads(true); + } + LOG_NOTICE(GENERAL, "Removed pad configuration directory: %s", config_dir); + return true; + } + else if (is_interactive) + { + QMessageBox::warning(this, tr("Warning!"), tr("Failed to completely remove pad configuration directory!")); + LOG_FATAL(GENERAL, "Failed to completely remove pad configuration directory: %s\nError: %s", config_dir, fs::g_tls_error); + } + return false; +} + bool game_list_frame::RemoveShadersCache(const std::string& base_dir, bool is_interactive) { if (!fs::is_dir(base_dir)) @@ -1260,6 +1345,52 @@ void game_list_frame::BatchRemoveCustomConfigurations() Refresh(true); } +void game_list_frame::BatchRemoveCustomPadConfigurations() +{ + std::set serials; + for (const auto& game : m_game_data) + { + if (game->hasCustomPadConfig && !serials.count(game->info.serial)) + { + serials.emplace(game->info.serial); + } + } + const u32 total = serials.size(); + + if (total == 0) + { + QMessageBox::information(this, tr("Custom Pad Configuration Batch Removal"), tr("No files found"), QMessageBox::Ok); + return; + } + + progress_dialog* pdlg = new progress_dialog(tr("Removing all custom pad configurations"), tr("Cancel"), 0, total, this); + pdlg->setWindowTitle(tr("Custom Pad Configuration Batch Removal")); + pdlg->setAutoClose(false); + pdlg->setAutoReset(false); + pdlg->show(); + + u32 removed = 0; + for (const auto& serial : serials) + { + if (pdlg->wasCanceled()) + { + LOG_NOTICE(GENERAL, "Custom Pad Configuration Batch Removal was canceled. %d/%d custom pad configurations cleared", removed, total); + break; + } + QApplication::processEvents(); + + if (RemoveCustomPadConfiguration(serial)) + { + pdlg->SetValue(++removed); + } + } + + pdlg->setLabelText(tr("%0/%1 custom pad configurations cleared").arg(removed).arg(total)); + pdlg->setCancelButtonText(tr("OK")); + QApplication::beep(); + Refresh(true); +} + void game_list_frame::BatchRemoveShaderCaches() { std::set serials; @@ -1302,7 +1433,7 @@ void game_list_frame::BatchRemoveShaderCaches() QApplication::beep(); } -QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon, const QColor& compatibility_color) +QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon, bool paint_pad_config_icon, const QColor& compatibility_color) { const int device_pixel_ratio = devicePixelRatio(); const QSize original_size = img.size(); @@ -1318,11 +1449,26 @@ QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon painter.drawImage(QPoint(0, 0), img); } - if (paint_config_icon && !m_isListLayout) + if (!m_isListLayout && (paint_config_icon || paint_pad_config_icon)) { const int width = original_size.width() * 0.2; const QPoint origin = QPoint(original_size.width() - width, 0); - QImage custom_config_icon(":/Icons/custom_config_2.png"); + QString icon_path; + + if (paint_config_icon && paint_pad_config_icon) + { + icon_path = ":/Icons/combo_config_bordered.png"; + } + else if (paint_config_icon) + { + icon_path = ":/Icons/custom_config_2.png"; + } + else if (paint_pad_config_icon) + { + icon_path = ":/Icons/controllers_2.png"; + } + + QImage custom_config_icon(icon_path); custom_config_icon.setDevicePixelRatio(device_pixel_ratio); painter.drawImage(origin, custom_config_icon.scaled(QSize(width, width) * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation)); } @@ -1342,7 +1488,7 @@ QPixmap game_list_frame::PaintedPixmap(const QImage& img, bool paint_config_icon return QPixmap::fromImage(image.scaled(m_Icon_Size * device_pixel_ratio, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation)); } -void game_list_frame::ShowCustomConfigIcon(QTableWidgetItem* item, bool enabled) +void game_list_frame::ShowCustomConfigIcon(QTableWidgetItem* item, config::type type) { auto game = GetGameInfoFromItem(item); if (game == nullptr) @@ -1350,24 +1496,22 @@ void game_list_frame::ShowCustomConfigIcon(QTableWidgetItem* item, bool enabled) return; } - game->hasCustomConfig = enabled; - const QColor color = getGridCompatibilityColor(game->compat.color); - game->pxmap = PaintedPixmap(game->icon, enabled, color); - if (!m_isListLayout) { + const QColor color = getGridCompatibilityColor(game->compat.color); + game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); int r = m_xgrid->currentItem()->row(), c = m_xgrid->currentItem()->column(); m_xgrid->addItem(game->pxmap, qstr(game->info.name).simplified(), r, c); m_xgrid->item(r, c)->setData(gui::game_role, QVariant::fromValue(game)); } - else if (enabled) - { + else if (game->hasCustomConfig && game->hasCustomPadConfig) + m_gameList->item(item->row(), gui::column_name)->setIcon(QIcon(":/Icons/combo_config_bordered.png")); + else if (game->hasCustomConfig) m_gameList->item(item->row(), gui::column_name)->setIcon(QIcon(":/Icons/custom_config.png")); - } + else if (game->hasCustomPadConfig) + m_gameList->item(item->row(), gui::column_name)->setIcon(QIcon(":/Icons/controllers.png")); else - { m_gameList->setItem(item->row(), gui::column_name, new custom_table_widget_item(game->info.name)); - } } void game_list_frame::ResizeIcons(const int& sliderPos) @@ -1395,7 +1539,7 @@ void game_list_frame::RepaintIcons(const bool& fromSettings) for (auto& game : m_game_data) { QColor color = getGridCompatibilityColor(game->compat.color); - game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, color); + game->pxmap = PaintedPixmap(game->icon, game->hasCustomConfig, game->hasCustomPadConfig, color); } Refresh(); @@ -1560,10 +1704,18 @@ int game_list_frame::PopulateGameList() // Title custom_table_widget_item* title_item = new custom_table_widget_item(title); - if (game->hasCustomConfig) + if (game->hasCustomConfig && game->hasCustomPadConfig) + { + title_item->setIcon(QIcon(":/Icons/combo_config_bordered.png")); + } + else if (game->hasCustomConfig) { title_item->setIcon(QIcon(":/Icons/custom_config.png")); } + else if (game->hasCustomPadConfig) + { + title_item->setIcon(QIcon(":/Icons/controllers.png")); + } // Serial custom_table_widget_item* serial_item = new custom_table_widget_item(game->info.serial); diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 6d415eb5dc..f130f1130f 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -31,6 +31,15 @@ enum Category Others, }; +namespace config +{ + enum class type + { + emu, + pad + }; +} + namespace category // (see PARAM.SFO in psdevwiki.com) TODO: Disc Categories { // PS3 bootable @@ -171,6 +180,7 @@ struct gui_game_info QImage icon; QPixmap pxmap; bool hasCustomConfig; + bool hasCustomPadConfig; }; typedef std::shared_ptr game_info; @@ -215,6 +225,7 @@ public Q_SLOTS: void BatchRemovePPUCaches(); void BatchRemoveSPUCaches(); void BatchRemoveCustomConfigurations(); + void BatchRemoveCustomPadConfigurations(); void BatchRemoveShaderCaches(); void SetListMode(const bool& isList); void SetSearchText(const QString& text); @@ -234,9 +245,9 @@ protected: void resizeEvent(QResizeEvent *event) override; bool eventFilter(QObject *object, QEvent *event) override; private: - QPixmap PaintedPixmap(const QImage& img, bool paint_config_icon = false, const QColor& color = QColor()); + QPixmap PaintedPixmap(const QImage& img, bool paint_config_icon = false, bool paint_pad_config_icon = false, const QColor& color = QColor()); QColor getGridCompatibilityColor(const QString& string); - void ShowCustomConfigIcon(QTableWidgetItem* item, bool enabled); + void ShowCustomConfigIcon(QTableWidgetItem* item, config::type type); void PopulateGameGrid(int maxCols, const QSize& image_size, const QColor& image_color); bool IsEntryVisible(const game_info& game); void SortGameList(); @@ -244,7 +255,8 @@ private: int PopulateGameList(); bool SearchMatchesApp(const std::string& name, const std::string& serial); - bool RemoveCustomConfiguration(const std::string& base_dir, bool is_interactive = false); + bool RemoveCustomConfiguration(const std::string& title_id, game_info game = nullptr, bool is_interactive = false); + bool RemoveCustomPadConfiguration(const std::string& title_id, game_info game = nullptr, bool is_interactive = false); bool RemoveShadersCache(const std::string& base_dir, bool is_interactive = false); bool RemovePPUCache(const std::string& base_dir, bool is_interactive = false); bool RemoveSPUCache(const std::string& base_dir, bool is_interactive = false); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 6a1f628fc8..bbb57e8766 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1247,6 +1247,7 @@ void main_window::CreateConnects() connect(ui->batchRemoveSPUCachesAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveSPUCaches); connect(ui->batchRemoveShaderCachesAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveShaderCaches); connect(ui->batchRemoveCustomConfigurationsAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveCustomConfigurations); + connect(ui->batchRemoveCustomPadConfigurationsAct, &QAction::triggered, m_gameListFrame, &game_list_frame::BatchRemoveCustomPadConfigurations); connect(ui->sysPauseAct, &QAction::triggered, this, &main_window::OnPlayOrPause); connect(ui->sysStopAct, &QAction::triggered, [=]() { Emu.Stop(); }); @@ -1282,20 +1283,19 @@ void main_window::CreateConnects() auto openPadSettings = [this] { - auto resetPadHandlers = [this](int/* result*/) - { - if (Emu.IsStopped()) - { - return; - } - Emu.GetCallbacks().reset_pads(); - }; if (!Emu.IsStopped()) { Emu.GetCallbacks().enable_pads(false); } pad_settings_dialog dlg(this); - connect(&dlg, &QDialog::finished, resetPadHandlers); + connect(&dlg, &QDialog::finished, [this](int/* result*/) + { + if (Emu.IsStopped()) + { + return; + } + Emu.GetCallbacks().reset_pads(Emu.GetTitleID()); + }); dlg.exec(); if (!Emu.IsStopped()) { diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index c3e6967705..4075cdba42 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -141,7 +141,7 @@ 0 0 1058 - 21 + 22 @@ -172,6 +172,7 @@ + @@ -995,6 +996,11 @@ Remove Shader Caches + + + Remove Custom Pad Configurations + + diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index edd7599914..df19f5092d 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -29,19 +29,13 @@ constexpr auto qstr = QString::fromStdString; inline bool CreateConfigFile(const QString& dir, const QString& name) { - QString input_dir = qstr(fs::get_config_dir()) + "/InputConfigs/"; - if (!QDir().mkdir(input_dir) && !QDir().exists(input_dir)) - { - LOG_ERROR(GENERAL, "Failed to create dir %s", sstr(input_dir)); - return false; - } - if (!QDir().mkdir(dir) && !QDir().exists(dir)) + if (!QDir().mkpath(dir)) { LOG_ERROR(GENERAL, "Failed to create dir %s", sstr(dir)); return false; } - QString filename = dir + name + ".yml"; + const QString filename = dir + name + ".yml"; QFile new_file(filename); if (!new_file.open(QIODevice::WriteOnly)) @@ -54,16 +48,25 @@ inline bool CreateConfigFile(const QString& dir, const QString& name) return true; }; -pad_settings_dialog::pad_settings_dialog(QWidget *parent) +pad_settings_dialog::pad_settings_dialog(QWidget *parent, const GameInfo *game) : QDialog(parent), ui(new Ui::pad_settings_dialog) { ui->setupUi(this); - setWindowTitle(tr("Gamepads Settings")); - // load input config g_cfg_input.from_default(); - g_cfg_input.load(); + + if (game) + { + m_title_id = game->serial; + g_cfg_input.load(game->serial); + setWindowTitle(tr("Gamepads Settings: [%0] %1").arg(qstr(game->serial)).arg(qstr(game->name).simplified())); + } + else + { + g_cfg_input.load(); + setWindowTitle(tr("Gamepads Settings")); + } // Create tab widget for 7 players m_tabs = new QTabWidget; @@ -153,7 +156,7 @@ pad_settings_dialog::pad_settings_dialog(QWidget *parent) QMessageBox::warning(this, tr("Error"), tr("Please choose a non-existing name")); continue; } - if (CreateConfigFile(qstr(PadHandlerBase::get_config_dir(g_cfg_input.player[i]->handler)), friendlyName)) + if (CreateConfigFile(qstr(PadHandlerBase::get_config_dir(g_cfg_input.player[i]->handler, m_title_id)), friendlyName)) { ui->chooseProfile->addItem(friendlyName); ui->chooseProfile->setCurrentText(friendlyName); @@ -839,6 +842,7 @@ void pad_settings_dialog::OnPadButtonClicked(int id) void pad_settings_dialog::OnTabChanged(int index) { + // TODO: Do not save yet! But keep all profile changes until the dialog was saved. // Save old profile SaveProfile(); @@ -898,7 +902,7 @@ void pad_settings_dialog::ChangeInputType() if (!g_cfg_input.player[player]->handler.from_string(handler)) { // Something went wrong - LOG_ERROR(GENERAL, "Failed to convert input string:%s", handler); + LOG_ERROR(GENERAL, "Failed to convert input string: %s", handler); return; } @@ -963,7 +967,7 @@ void pad_settings_dialog::ChangeInputType() } } - QString profile_dir = qstr(PadHandlerBase::get_config_dir(m_handler->m_type)); + QString profile_dir = qstr(PadHandlerBase::get_config_dir(m_handler->m_type, m_title_id)); QStringList profiles = gui::utils::get_dir_entries(QDir(profile_dir), QStringList() << "*.yml"); if (profiles.isEmpty()) @@ -1021,7 +1025,7 @@ void pad_settings_dialog::ChangeProfile() } // Change handler - const std::string cfg_name = PadHandlerBase::get_config_dir(m_handler->m_type) + m_profile + ".yml"; + const std::string cfg_name = PadHandlerBase::get_config_dir(m_handler->m_type, m_title_id) + m_profile + ".yml"; // Adjust to the different pad handlers switch (m_handler->m_type) @@ -1145,7 +1149,7 @@ void pad_settings_dialog::SaveExit() g_cfg_input.player[i]->profile.from_default(); } - g_cfg_input.save(); + g_cfg_input.save(m_title_id); QDialog::accept(); } diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index e14b00ad37..0541b7589d 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -8,6 +8,7 @@ #include #include "Emu/Io/PadHandler.h" +#include "Emu/GameInfo.h" namespace Ui { @@ -85,7 +86,7 @@ class pad_settings_dialog : public QDialog const QString Disconnected_suffix = tr(" (disconnected)"); public: - explicit pad_settings_dialog(QWidget *parent = nullptr); + explicit pad_settings_dialog(QWidget *parent = nullptr, const GameInfo *game = nullptr); ~pad_settings_dialog(); private Q_SLOTS: @@ -100,6 +101,7 @@ private Q_SLOTS: private: Ui::pad_settings_dialog *ui; + std::string m_title_id; // TabWidget QTabWidget* m_tabs;