diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 6b1ab732d..50f949e64 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -586,9 +586,10 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) } si.SetStringValue("MemoryCards", "Card1Type", Settings::GetMemoryCardTypeName(Settings::DEFAULT_MEMORY_CARD_1_TYPE)); - si.SetStringValue("MemoryCards", "Card1Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_1.mcd"); si.SetStringValue("MemoryCards", "Card2Type", Settings::GetMemoryCardTypeName(Settings::DEFAULT_MEMORY_CARD_2_TYPE)); - si.SetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd"); + si.DeleteValue("MemoryCards", "Card1Path"); + si.DeleteValue("MemoryCards", "Card2Path"); + si.DeleteValue("MemoryCards", "Directory"); si.SetBoolValue("MemoryCards", "UsePlaylistTitle", true); si.SetStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(Settings::DEFAULT_MULTITAP_MODE)); @@ -837,7 +838,8 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings) if (g_settings.memory_card_types != old_settings.memory_card_types || g_settings.memory_card_paths != old_settings.memory_card_paths || (g_settings.memory_card_use_playlist_title != old_settings.memory_card_use_playlist_title && - System::HasMediaSubImages())) + System::HasMediaSubImages()) || + g_settings.memory_card_directory != old_settings.memory_card_directory) { System::UpdateMemoryCards(); } @@ -952,14 +954,38 @@ TinyString HostInterface::GetTimestampStringForFileName() return str; } +std::string HostInterface::GetMemoryCardDirectory() const +{ + if (g_settings.memory_card_directory.empty()) + return GetUserDirectoryRelativePath("memcards"); + else + return g_settings.memory_card_directory; +} + std::string HostInterface::GetSharedMemoryCardPath(u32 slot) const { - return GetUserDirectoryRelativePath("memcards" FS_OSPATH_SEPARATOR_STR "shared_card_%u.mcd", slot + 1); + if (g_settings.memory_card_directory.empty()) + { + return GetUserDirectoryRelativePath("memcards" FS_OSPATH_SEPARATOR_STR "shared_card_%u.mcd", slot + 1); + } + else + { + return StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "shared_card_%u.mcd", + g_settings.memory_card_directory.c_str(), slot + 1); + } } std::string HostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot) const { - return GetUserDirectoryRelativePath("memcards" FS_OSPATH_SEPARATOR_STR "%s_%u.mcd", game_code, slot + 1); + if (g_settings.memory_card_directory.empty()) + { + return GetUserDirectoryRelativePath("memcards" FS_OSPATH_SEPARATOR_STR "%s_%u.mcd", game_code, slot + 1); + } + else + { + return StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "%s_%u.mcd", + g_settings.memory_card_directory.c_str(), game_code, slot + 1); + } } bool HostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /*= false*/) diff --git a/src/core/host_interface.h b/src/core/host_interface.h index 641ced6a0..20edd48ab 100644 --- a/src/core/host_interface.h +++ b/src/core/host_interface.h @@ -93,6 +93,9 @@ public: /// Retrieves information about specified game from game list. virtual void GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title); + /// Returns the directory where per-game memory cards will be saved. + virtual std::string GetMemoryCardDirectory() const; + /// Returns the default path to a memory card. virtual std::string GetSharedMemoryCardPath(u32 slot) const; diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 061477cf9..81ae46a27 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -275,14 +275,13 @@ void Settings::Load(SettingsInterface& si) ParseMemoryCardTypeName( si.GetStringValue("MemoryCards", "Card1Type", GetMemoryCardTypeName(DEFAULT_MEMORY_CARD_1_TYPE)).c_str()) .value_or(DEFAULT_MEMORY_CARD_1_TYPE); - memory_card_paths[0] = - si.GetStringValue("MemoryCards", "Card1Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_1.mcd"); memory_card_types[1] = ParseMemoryCardTypeName( si.GetStringValue("MemoryCards", "Card2Type", GetMemoryCardTypeName(DEFAULT_MEMORY_CARD_2_TYPE)).c_str()) .value_or(DEFAULT_MEMORY_CARD_2_TYPE); - memory_card_paths[1] = - si.GetStringValue("MemoryCards", "Card2Path", "memcards" FS_OSPATH_SEPARATOR_STR "shared_card_2.mcd"); + memory_card_paths[0] = si.GetStringValue("MemoryCards", "Card1Path", ""); + memory_card_paths[1] = si.GetStringValue("MemoryCards", "Card2Path", ""); + memory_card_directory = si.GetStringValue("MemoryCards", "Directory", ""); memory_card_use_playlist_title = si.GetBoolValue("MemoryCards", "UsePlaylistTitle", true); multitap_mode = @@ -434,9 +433,21 @@ void Settings::Save(SettingsInterface& si) const } si.SetStringValue("MemoryCards", "Card1Type", GetMemoryCardTypeName(memory_card_types[0])); - si.SetStringValue("MemoryCards", "Card1Path", memory_card_paths[0].c_str()); si.SetStringValue("MemoryCards", "Card2Type", GetMemoryCardTypeName(memory_card_types[1])); - si.SetStringValue("MemoryCards", "Card2Path", memory_card_paths[1].c_str()); + if (!memory_card_paths[0].empty()) + si.SetStringValue("MemoryCards", "Card1Path", memory_card_paths[0].c_str()); + else + si.DeleteValue("MemoryCards", "Card1Path"); + + if (!memory_card_paths[1].empty()) + si.SetStringValue("MemoryCards", "Card2Path", memory_card_paths[1].c_str()); + else + si.DeleteValue("MemoryCards", "Card2Path"); + + if (!memory_card_directory.empty()) + si.SetStringValue("MemoryCards", "Directory", memory_card_directory.c_str()); + else + si.DeleteValue("MemoryCards", "Directory"); si.SetBoolValue("MemoryCards", "UsePlaylistTitle", memory_card_use_playlist_title); si.SetStringValue("ControllerPorts", "MultitapMode", GetMultitapModeName(multitap_mode)); diff --git a/src/core/settings.h b/src/core/settings.h index 1531bb11e..352ab4d5b 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -216,6 +216,7 @@ struct Settings std::array memory_card_types{}; std::array memory_card_paths{}; + std::string memory_card_directory; bool memory_card_use_playlist_title = true; MultitapMode multitap_mode = MultitapMode::Disabled; diff --git a/src/core/system.cpp b/src/core/system.cpp index b704b31f8..5b5c8b474 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -1821,17 +1821,9 @@ void UpdateMemoryCards() case MemoryCardType::Shared: { if (g_settings.memory_card_paths[i].empty()) - { - g_host_interface->AddFormattedOSDMessage( - 10.0f, - g_host_interface->TranslateString("System", "Memory card path for slot %u is missing, using default."), - i + 1u); card = MemoryCard::Open(g_host_interface->GetSharedMemoryCardPath(i)); - } else - { card = MemoryCard::Open(g_settings.memory_card_paths[i]); - } } break; } diff --git a/src/duckstation-qt/memorycardsettingswidget.cpp b/src/duckstation-qt/memorycardsettingswidget.cpp index abb525fb5..7895498a9 100644 --- a/src/duckstation-qt/memorycardsettingswidget.cpp +++ b/src/duckstation-qt/memorycardsettingswidget.cpp @@ -38,6 +38,32 @@ void MemoryCardSettingsWidget::createUi(SettingsDialog* dialog) QGroupBox* box = new QGroupBox(tr("Shared Settings"), this); QVBoxLayout* box_layout = new QVBoxLayout(box); + { + QLabel* label = new QLabel(tr("Memory Card Directory:"), box); + box_layout->addWidget(label); + + QHBoxLayout* hbox = new QHBoxLayout(); + m_memory_card_directory = new QLineEdit(box); + SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, m_memory_card_directory, "MemoryCards", + "Directory"); + if (m_memory_card_directory->text().isEmpty()) + { + QSignalBlocker sb(m_memory_card_directory); + m_memory_card_directory->setText(QString::fromStdString(m_host_interface->GetMemoryCardDirectory())); + } + hbox->addWidget(m_memory_card_directory); + + QPushButton* browse = new QPushButton(tr("Browse..."), box); + connect(browse, &QPushButton::clicked, this, &MemoryCardSettingsWidget::onBrowseMemCardsDirectoryClicked); + hbox->addWidget(browse); + + QPushButton* reset = new QPushButton(tr("Reset"), box); + connect(reset, &QPushButton::clicked, this, &MemoryCardSettingsWidget::onResetMemCardsDirectoryClicked); + hbox->addWidget(reset); + + box_layout->addLayout(hbox); + } + QCheckBox* playlist_title_as_game_title = new QCheckBox(tr("Use Single Card For Sub-Images"), box); SettingWidgetBinder::BindWidgetToBoolSetting(m_host_interface, playlist_title_as_game_title, "MemoryCards", "UsePlaylistTitle", true); @@ -47,18 +73,21 @@ void MemoryCardSettingsWidget::createUi(SettingsDialog* dialog) tr("When using a multi-disc format (m3u/pbp) and per-game (title) memory cards, a single memory card " "will be used for all discs. If unchecked, a separate card will be used for each disc.")); - QHBoxLayout* note_layout = new QHBoxLayout(); - QLabel* note_label = - new QLabel(tr("If one of the \"separate card per game\" memory card modes is chosen, these memory " - "cards will be saved to the memcards directory."), - box); - note_label->setWordWrap(true); - note_layout->addWidget(note_label, 1); + { - QPushButton* open_memcards = new QPushButton(tr("Open..."), box); - connect(open_memcards, &QPushButton::clicked, this, &MemoryCardSettingsWidget::onOpenMemCardsDirectoryClicked); - note_layout->addWidget(open_memcards); - box_layout->addLayout(note_layout); + QHBoxLayout* note_layout = new QHBoxLayout(); + QLabel* note_label = + new QLabel(tr("If one of the \"separate card per game\" memory card modes is chosen, these memory " + "cards will be saved to the memory cards directory."), + box); + note_label->setWordWrap(true); + note_layout->addWidget(note_label, 1); + + QPushButton* open_memcards = new QPushButton(tr("Open..."), box); + connect(open_memcards, &QPushButton::clicked, this, &MemoryCardSettingsWidget::onOpenMemCardsDirectoryClicked); + note_layout->addWidget(open_memcards); + box_layout->addLayout(note_layout); + } layout->addWidget(box); } @@ -91,12 +120,22 @@ void MemoryCardSettingsWidget::createPortSettingsUi(SettingsDialog* dialog, int ui->memory_card_path = new QLineEdit(ui->container); SettingWidgetBinder::BindWidgetToStringSetting(m_host_interface, ui->memory_card_path, "MemoryCards", StringUtil::StdStringFromFormat("Card%dPath", index + 1)); + if (ui->memory_card_path->text().isEmpty()) + { + QSignalBlocker sb(ui->memory_card_path); + ui->memory_card_path->setText( + QString::fromStdString(m_host_interface->GetSharedMemoryCardPath(static_cast(index)))); + } memory_card_layout->addWidget(ui->memory_card_path); QPushButton* memory_card_path_browse = new QPushButton(tr("Browse..."), ui->container); connect(memory_card_path_browse, &QPushButton::clicked, [this, index]() { onBrowseMemoryCardPathClicked(index); }); memory_card_layout->addWidget(memory_card_path_browse); + QPushButton* memory_card_path_reset = new QPushButton(tr("Reset"), ui->container); + connect(memory_card_path_reset, &QPushButton::clicked, [this, index]() { onResetMemoryCardPathClicked(index); }); + memory_card_layout->addWidget(memory_card_path_reset); + ui->layout->addWidget(new QLabel(tr("Shared Memory Card Path:"), ui->container)); ui->layout->addLayout(memory_card_layout); @@ -113,8 +152,38 @@ void MemoryCardSettingsWidget::onBrowseMemoryCardPathClicked(int index) m_port_ui[index].memory_card_path->setText(path); } +void MemoryCardSettingsWidget::onResetMemoryCardPathClicked(int index) +{ + m_host_interface->RemoveSettingValue("MemoryCards", TinyString::FromFormat("Card%dPath", index + 1)); + m_host_interface->applySettings(); + + QSignalBlocker db(m_port_ui[index].memory_card_path); + m_port_ui[index].memory_card_path->setText(QString::fromStdString(m_host_interface->GetSharedMemoryCardPath(index))); +} + void MemoryCardSettingsWidget::onOpenMemCardsDirectoryClicked() { - QtUtils::OpenURL(this, - QUrl::fromLocalFile(m_host_interface->getUserDirectoryRelativePath(QStringLiteral("memcards")))); + QtUtils::OpenURL(this, QUrl::fromLocalFile(m_memory_card_directory->text())); +} + +void MemoryCardSettingsWidget::onBrowseMemCardsDirectoryClicked() +{ + QString path = + QDir::toNativeSeparators(QFileDialog::getExistingDirectory(this, tr("Select path to memory card directory"))); + if (path.isEmpty()) + return; + + m_memory_card_directory->setText(path); + m_host_interface->applySettings(); +} + +void MemoryCardSettingsWidget::onResetMemCardsDirectoryClicked() +{ + m_host_interface->RemoveSettingValue("MemoryCards", "Directory"); + m_host_interface->applySettings(); + + // This sucks.. settings are applied asynchronously, so we have to manually build the path here. + QString memory_card_directory(m_host_interface->getUserDirectoryRelativePath(QStringLiteral("memcards"))); + QSignalBlocker db(m_memory_card_directory); + m_memory_card_directory->setText(memory_card_directory); } diff --git a/src/duckstation-qt/memorycardsettingswidget.h b/src/duckstation-qt/memorycardsettingswidget.h index bc0109619..49ec2cc66 100644 --- a/src/duckstation-qt/memorycardsettingswidget.h +++ b/src/duckstation-qt/memorycardsettingswidget.h @@ -33,7 +33,11 @@ private: void createUi(SettingsDialog* dialog); void createPortSettingsUi(SettingsDialog* dialog, int index, PortSettingsUI* ui); void onBrowseMemoryCardPathClicked(int index); + void onResetMemoryCardPathClicked(int index); void onOpenMemCardsDirectoryClicked(); + void onBrowseMemCardsDirectoryClicked(); + void onResetMemCardsDirectoryClicked(); std::array m_port_ui = {}; + QLineEdit* m_memory_card_directory = nullptr; }; diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index d54e53748..c6791a375 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -1804,10 +1804,32 @@ void DrawSettingsWindow() &s_settings_copy.memory_card_use_playlist_title); static std::string memory_card_directory; - if (memory_card_directory.empty()) - memory_card_directory = s_host_interface->GetUserDirectoryRelativePath("memcards"); + static bool memory_card_directory_set = false; + if (!memory_card_directory_set) + { + memory_card_directory = s_host_interface->GetMemoryCardDirectory(); + memory_card_directory_set = true; + } - MenuButton("Per-Game Memory Card Directory", memory_card_directory.c_str(), false); + if (MenuButton("Memory Card Directory", memory_card_directory.c_str())) + { + OpenFileSelector("Memory Card Directory", true, [](const std::string& path) { + if (!path.empty()) + { + memory_card_directory = path; + s_settings_copy.memory_card_directory = path; + s_host_interface->RunLater(SaveAndApplySettings); + } + CloseFileSelector(); + }); + } + + if (MenuButton("Reset Memory Card Directory", "Resets memory card directory to default (user directory).")) + { + s_settings_copy.memory_card_directory.clear(); + s_host_interface->RunLater(SaveAndApplySettings); + memory_card_directory_set = false; + } EndMenuButtons(); }