summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorAdmiral H. Curtiss <pikachu025@gmail.com>2022-01-19 03:05:05 +0100
committerAdmiral H. Curtiss <pikachu025@gmail.com>2022-11-22 10:48:37 +0100
commit2132e005c32e137f8723abb68b314f57e02eb2e7 (patch)
tree76e4dcaf1764a7580b6c477522de7ae4b90e0860 /Source
parent8f5a58f8be7e6979e277eb16934b140e4bbf10a4 (diff)
Core: Add config setting for base GCI folder path.
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/Core/Config/MainSettings.cpp71
-rw-r--r--Source/Core/Core/Config/MainSettings.h7
-rw-r--r--Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp2
-rw-r--r--Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp14
-rw-r--r--Source/Core/Core/NetPlayServer.cpp6
-rw-r--r--Source/Core/DolphinQt/GameList/GameList.cpp12
-rw-r--r--Source/Core/DolphinQt/Settings/GameCubePane.cpp132
-rw-r--r--Source/Core/DolphinQt/Settings/GameCubePane.h6
8 files changed, 231 insertions, 19 deletions
diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp
index 08b3d586f1..091fd3504f 100644
--- a/Source/Core/Core/Config/MainSettings.cpp
+++ b/Source/Core/Core/Config/MainSettings.cpp
@@ -76,6 +76,18 @@ const Info<std::string>& GetInfoForAGPCartPath(ExpansionInterface::Slot slot)
};
return *infos[slot];
}
+const Info<std::string> MAIN_GCI_FOLDER_A_PATH{{System::Main, "Core", "GCIFolderAPath"}, ""};
+const Info<std::string> MAIN_GCI_FOLDER_B_PATH{{System::Main, "Core", "GCIFolderBPath"}, ""};
+const Info<std::string>& GetInfoForGCIPath(ExpansionInterface::Slot slot)
+{
+ ASSERT(ExpansionInterface::IsMemcardSlot(slot));
+ static constexpr Common::EnumMap<const Info<std::string>*, ExpansionInterface::MAX_MEMCARD_SLOT>
+ infos{
+ &MAIN_GCI_FOLDER_A_PATH,
+ &MAIN_GCI_FOLDER_B_PATH,
+ };
+ return *infos[slot];
+}
const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE{
{System::Main, "Core", "GCIFolderAPathOverride"}, ""};
const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE{
@@ -650,4 +662,63 @@ bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot)
{
return Config::Get(GetInfoForMemcardPath(slot)).empty();
}
+
+std::string GetGCIFolderPath(ExpansionInterface::Slot slot, std::optional<DiscIO::Region> region)
+{
+ return GetGCIFolderPath(Config::Get(GetInfoForGCIPath(slot)), slot, region);
+}
+
+std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::Slot slot,
+ std::optional<DiscIO::Region> region)
+{
+ if (configured_folder.empty())
+ {
+ const auto region_dir = Config::GetDirectoryForRegion(
+ Config::ToGameCubeRegion(region ? *region : Config::Get(Config::MAIN_FALLBACK_REGION)));
+ const bool is_slot_a = slot == ExpansionInterface::Slot::A;
+ return fmt::format("{}{}/Card {}", File::GetUserPath(D_GCUSER_IDX), region_dir,
+ is_slot_a ? 'A' : 'B');
+ }
+
+ // Custom path is expected to be stored in the form of
+ // "/path/to/folder/{region_code}"
+ // with an arbitrary but supported region code.
+ // Try to extract and replace that region code.
+ // If there's no region code just insert one at the end.
+
+ UnifyPathSeparators(configured_folder);
+ while (StringEndsWith(configured_folder, "/"))
+ configured_folder.pop_back();
+
+ constexpr std::string_view us_region = "/" USA_DIR;
+ constexpr std::string_view jp_region = "/" JAP_DIR;
+ constexpr std::string_view eu_region = "/" EUR_DIR;
+ std::string_view base_path = configured_folder;
+ std::optional<DiscIO::Region> path_region = std::nullopt;
+ if (StringEndsWith(base_path, us_region))
+ {
+ base_path = base_path.substr(0, base_path.size() - us_region.size());
+ path_region = DiscIO::Region::NTSC_U;
+ }
+ else if (StringEndsWith(base_path, jp_region))
+ {
+ base_path = base_path.substr(0, base_path.size() - jp_region.size());
+ path_region = DiscIO::Region::NTSC_J;
+ }
+ else if (StringEndsWith(base_path, eu_region))
+ {
+ base_path = base_path.substr(0, base_path.size() - eu_region.size());
+ path_region = DiscIO::Region::PAL;
+ }
+
+ const DiscIO::Region used_region =
+ region ? *region : (path_region ? *path_region : Config::Get(Config::MAIN_FALLBACK_REGION));
+ return fmt::format("{}/{}", base_path,
+ Config::GetDirectoryForRegion(Config::ToGameCubeRegion(used_region)));
+}
+
+bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot)
+{
+ return Config::Get(GetInfoForGCIPath(slot)).empty();
+}
} // namespace Config
diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h
index 4aab4dac1c..f30ef02a38 100644
--- a/Source/Core/Core/Config/MainSettings.h
+++ b/Source/Core/Core/Config/MainSettings.h
@@ -75,6 +75,9 @@ const Info<std::string>& GetInfoForMemcardPath(ExpansionInterface::Slot slot);
extern const Info<std::string> MAIN_AGP_CART_A_PATH;
extern const Info<std::string> MAIN_AGP_CART_B_PATH;
const Info<std::string>& GetInfoForAGPCartPath(ExpansionInterface::Slot slot);
+extern const Info<std::string> MAIN_GCI_FOLDER_A_PATH;
+extern const Info<std::string> MAIN_GCI_FOLDER_B_PATH;
+const Info<std::string>& GetInfoForGCIPath(ExpansionInterface::Slot slot);
extern const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE;
extern const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE;
const Info<std::string>& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot);
@@ -352,4 +355,8 @@ std::string GetMemcardPath(ExpansionInterface::Slot slot, std::optional<DiscIO::
std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot,
std::optional<DiscIO::Region> region, u16 size_mb = 0x80);
bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot);
+std::string GetGCIFolderPath(ExpansionInterface::Slot slot, std::optional<DiscIO::Region> region);
+std::string GetGCIFolderPath(std::string configured_folder, ExpansionInterface::Slot slot,
+ std::optional<DiscIO::Region> region);
+bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot);
} // namespace Config
diff --git a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
index 97fda95b3b..bd7f28fe3c 100644
--- a/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
+++ b/Source/Core/Core/ConfigLoaders/IsSettingSaveable.cpp
@@ -131,6 +131,8 @@ bool IsSettingSaveable(const Config::Location& config_location)
&Config::MAIN_SYNC_GPU_MIN_DISTANCE.GetLocation(),
&Config::MAIN_SYNC_GPU_OVERCLOCK.GetLocation(),
&Config::MAIN_OVERRIDE_BOOT_IOS.GetLocation(),
+ &Config::MAIN_GCI_FOLDER_A_PATH.GetLocation(),
+ &Config::MAIN_GCI_FOLDER_B_PATH.GetLocation(),
// UI.General
diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp
index 2aa5c1bbb5..c054c47404 100644
--- a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp
+++ b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp
@@ -154,20 +154,20 @@ CEXIMemoryCard::GetGCIFolderPath(Slot card_slot, AllowMovieFolder allow_movie_fo
if (!path_override.empty())
return {std::move(path_override), false};
- std::string path = File::GetUserPath(D_GCUSER_IDX);
-
const bool use_movie_folder = allow_movie_folder == AllowMovieFolder::Yes &&
Movie::IsPlayingInput() && Movie::IsConfigSaved() &&
Movie::IsUsingMemcard(card_slot) &&
Movie::IsStartingFromClearSave();
+ const DiscIO::Region region = Config::ToGameCubeRegion(SConfig::GetInstance().m_region);
if (use_movie_folder)
- path += "Movie" DIR_SEP;
+ {
+ return {fmt::format("{}{}/Movie/Card {}", File::GetUserPath(D_GCUSER_IDX),
+ Config::GetDirectoryForRegion(region), s_card_short_names[card_slot]),
+ false};
+ }
- const DiscIO::Region region = Config::ToGameCubeRegion(SConfig::GetInstance().m_region);
- path = path + Config::GetDirectoryForRegion(region) + DIR_SEP +
- fmt::format("Card {}", s_card_short_names[card_slot]);
- return {std::move(path), !use_movie_folder};
+ return {Config::GetGCIFolderPath(card_slot, region), true};
}
void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp
index 18076ada29..ed2110ad16 100644
--- a/Source/Core/Core/NetPlayServer.cpp
+++ b/Source/Core/Core/NetPlayServer.cpp
@@ -1686,7 +1686,8 @@ bool NetPlayServer::SyncSaveData(const SaveSyncInfo& sync_info)
return true;
const auto game_region = sync_info.game->GetRegion();
- const std::string region = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(game_region));
+ const auto gamecube_region = Config::ToGameCubeRegion(game_region);
+ const std::string region = Config::GetDirectoryForRegion(gamecube_region);
for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{
@@ -1723,8 +1724,7 @@ bool NetPlayServer::SyncSaveData(const SaveSyncInfo& sync_info)
else if (Config::Get(Config::GetInfoForEXIDevice(slot)) ==
ExpansionInterface::EXIDeviceType::MemoryCardFolder)
{
- const std::string path = File::GetUserPath(D_GCUSER_IDX) + region + DIR_SEP +
- fmt::format("Card {}", is_slot_a ? 'A' : 'B');
+ const std::string path = Config::GetGCIFolderPath(slot, gamecube_region);
sf::Packet pac;
pac << MessageID::SyncSaveData;
diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp
index db86079825..69afc13a74 100644
--- a/Source/Core/DolphinQt/GameList/GameList.cpp
+++ b/Source/Core/DolphinQt/GameList/GameList.cpp
@@ -716,16 +716,10 @@ void GameList::OpenGCSaveFolder()
{
case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
{
- std::string path = fmt::format("{}/{}/{}", File::GetUserPath(D_GCUSER_IDX),
- Config::GetDirectoryForRegion(game->GetRegion()),
- slot == Slot::A ? "Card A" : "Card B");
-
std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot));
-
- if (!override_path.empty())
- path = override_path;
-
- QDir dir(QString::fromStdString(path));
+ QDir dir(QString::fromStdString(override_path.empty() ?
+ Config::GetGCIFolderPath(slot, game->GetRegion()) :
+ override_path));
if (!dir.entryList({QStringLiteral("%1-%2-*.gci")
.arg(QString::fromStdString(game->GetMakerID()))
diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
index cc2b8fbe5f..b4e02c8305 100644
--- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp
+++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp
@@ -114,6 +114,12 @@ void GameCubePane::CreateWidgets()
m_agp_paths[slot] = new QLineEdit();
m_agp_path_layouts[slot]->addWidget(m_agp_path_labels[slot]);
m_agp_path_layouts[slot]->addWidget(m_agp_paths[slot]);
+
+ m_gci_path_layouts[slot] = new QHBoxLayout();
+ m_gci_path_labels[slot] = new QLabel(tr("GCI Folder Path:"));
+ m_gci_paths[slot] = new QLineEdit();
+ m_gci_path_layouts[slot]->addWidget(m_gci_path_labels[slot]);
+ m_gci_path_layouts[slot]->addWidget(m_gci_paths[slot]);
}
// Add slot devices
@@ -156,6 +162,9 @@ void GameCubePane::CreateWidgets()
device_layout->addLayout(m_agp_path_layouts[ExpansionInterface::Slot::A], row, 0, 1, 3);
++row;
+ device_layout->addLayout(m_gci_path_layouts[ExpansionInterface::Slot::A], row, 0, 1, 3);
+
+ ++row;
device_layout->addWidget(new QLabel(tr("Slot B:")), row, 0);
device_layout->addWidget(m_slot_combos[ExpansionInterface::Slot::B], row, 1);
device_layout->addWidget(m_slot_buttons[ExpansionInterface::Slot::B], row, 2);
@@ -167,6 +176,9 @@ void GameCubePane::CreateWidgets()
device_layout->addLayout(m_agp_path_layouts[ExpansionInterface::Slot::B], row, 0, 1, 3);
++row;
+ device_layout->addLayout(m_gci_path_layouts[ExpansionInterface::Slot::B], row, 0, 1, 3);
+
+ ++row;
device_layout->addWidget(new QLabel(tr("SP1:")), row, 0);
device_layout->addWidget(m_slot_combos[ExpansionInterface::Slot::SP1], row, 1);
device_layout->addWidget(m_slot_buttons[ExpansionInterface::Slot::SP1], row, 2);
@@ -249,6 +261,11 @@ void GameCubePane::ConnectWidgets()
});
connect(m_agp_paths[slot], &QLineEdit::editingFinished,
[this, slot] { SetAGPRom(slot, m_agp_paths[slot]->text()); });
+ connect(m_gci_paths[slot], &QLineEdit::editingFinished, [this, slot] {
+ // revert path change on failure
+ if (!SetGCIFolder(slot, m_gci_paths[slot]->text()))
+ LoadSettings();
+ });
}
#ifdef HAS_LIBMGBA
@@ -302,14 +319,19 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
case ExpansionInterface::Slot::B:
{
has_config = (device == ExpansionInterface::EXIDeviceType::MemoryCard ||
+ device == ExpansionInterface::EXIDeviceType::MemoryCardFolder ||
device == ExpansionInterface::EXIDeviceType::AGP ||
device == ExpansionInterface::EXIDeviceType::Microphone);
const bool hide_memory_card = device != ExpansionInterface::EXIDeviceType::MemoryCard ||
Config::IsDefaultMemcardPathConfigured(slot);
+ const bool hide_gci_path = device != ExpansionInterface::EXIDeviceType::MemoryCardFolder ||
+ Config::IsDefaultGCIFolderPathConfigured(slot);
m_memcard_path_labels[slot]->setHidden(hide_memory_card);
m_memcard_paths[slot]->setHidden(hide_memory_card);
m_agp_path_labels[slot]->setHidden(device != ExpansionInterface::EXIDeviceType::AGP);
m_agp_paths[slot]->setHidden(device != ExpansionInterface::EXIDeviceType::AGP);
+ m_gci_path_labels[slot]->setHidden(hide_gci_path);
+ m_gci_paths[slot]->setHidden(hide_gci_path);
break;
}
case ExpansionInterface::Slot::SP1:
@@ -332,6 +354,9 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
case ExpansionInterface::EXIDeviceType::MemoryCard:
BrowseMemcard(slot);
return;
+ case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
+ BrowseGCIFolder(slot);
+ return;
case ExpansionInterface::EXIDeviceType::AGP:
BrowseAGPRom(slot);
return;
@@ -462,6 +487,111 @@ bool GameCubePane::SetMemcard(ExpansionInterface::Slot slot, const QString& file
return true;
}
+void GameCubePane::BrowseGCIFolder(ExpansionInterface::Slot slot)
+{
+ ASSERT(ExpansionInterface::IsMemcardSlot(slot));
+
+ const QString path = DolphinFileDialog::getExistingDirectory(
+ this, tr("Choose the GCI base folder"),
+ QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)));
+
+ if (!path.isEmpty())
+ SetGCIFolder(slot, path);
+}
+
+bool GameCubePane::SetGCIFolder(ExpansionInterface::Slot slot, const QString& path)
+{
+ if (path.isEmpty())
+ {
+ ModalMessageBox::critical(this, tr("Error"), tr("Cannot set GCI folder to an empty path."));
+ return false;
+ }
+
+ std::string raw_path =
+ WithUnifiedPathSeparators(QFileInfo(path).absoluteFilePath().toStdString());
+ while (StringEndsWith(raw_path, "/"))
+ raw_path.pop_back();
+
+ // The user might be attempting to reset this path to its default, check for this.
+ const std::string default_jp_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::NTSC_J);
+ const std::string default_us_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::NTSC_U);
+ const std::string default_eu_path = Config::GetGCIFolderPath("", slot, DiscIO::Region::PAL);
+ const bool is_default_path =
+ raw_path == default_jp_path || raw_path == default_us_path || raw_path == default_eu_path;
+
+ bool path_changed;
+ if (is_default_path)
+ {
+ // Reset to default.
+ // Note that this does not need to check if the same card is in the other slot, because that's
+ // impossible given our constraints for folder names.
+ raw_path = "";
+ path_changed = !Config::IsDefaultGCIFolderPathConfigured(slot);
+ }
+ else
+ {
+ // Figure out if the user selected a folder that ends in a valid region specifier.
+ const std::string jp_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::NTSC_J);
+ const std::string us_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::NTSC_U);
+ const std::string eu_path = Config::GetGCIFolderPath(raw_path, slot, DiscIO::Region::PAL);
+ const bool raw_path_valid = raw_path == jp_path || raw_path == us_path || raw_path == eu_path;
+
+ if (!raw_path_valid)
+ {
+ // TODO: We could try to autodetect the card region here and offer automatic renaming.
+ ModalMessageBox::critical(
+ this, tr("Error"),
+ tr("The folder %1 does not conform to Dolphin's region code format "
+ "for GCI folders. Please rename this folder to either %2, %3, or "
+ "%4, matching the region of the save files that are in it.")
+ .arg(QString::fromStdString(PathToFileName(raw_path)))
+ .arg(QString::fromStdString(PathToFileName(us_path)))
+ .arg(QString::fromStdString(PathToFileName(eu_path)))
+ .arg(QString::fromStdString(PathToFileName(jp_path))));
+ return false;
+ }
+
+ // Check if the other slot has the same folder configured and refuse to use this folder if so.
+ // The EU path is compared here, but it doesn't actually matter which one we compare since they
+ // follow a known pattern, so if the EU path matches the other match too and vice-versa.
+ for (ExpansionInterface::Slot other_slot : ExpansionInterface::MEMCARD_SLOTS)
+ {
+ if (other_slot == slot)
+ continue;
+
+ const std::string other_eu_path = Config::GetGCIFolderPath(other_slot, DiscIO::Region::PAL);
+ if (eu_path == other_eu_path)
+ {
+ ModalMessageBox::critical(
+ this, tr("Error"),
+ tr("The same folder can't be used in multiple slots; it is already used by %1.")
+ .arg(QString::fromStdString(fmt::to_string(other_slot))));
+ return false;
+ }
+ }
+
+ path_changed = eu_path != Config::GetGCIFolderPath(slot, DiscIO::Region::PAL);
+ }
+
+ Config::SetBase(Config::GetInfoForGCIPath(slot), raw_path);
+
+ if (Core::IsRunning())
+ {
+ // If emulation is running and the new card is different from the old one, notify the system to
+ // eject the old and insert the new card.
+ // TODO: This should probably be done by a config change callback instead.
+ if (path_changed)
+ {
+ // ChangeDevice unplugs the device for 1 second, which means that games should notice that
+ // the path has changed and thus the memory card contents have changed
+ ExpansionInterface::ChangeDevice(slot, ExpansionInterface::EXIDeviceType::MemoryCardFolder);
+ }
+ }
+
+ LoadSettings();
+ return true;
+}
+
void GameCubePane::BrowseAGPRom(ExpansionInterface::Slot slot)
{
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
@@ -576,6 +706,8 @@ void GameCubePane::LoadSettings()
->setText(QString::fromStdString(Config::GetMemcardPath(slot, std::nullopt)));
SignalBlocking(m_agp_paths[slot])
->setText(QString::fromStdString(Config::Get(Config::GetInfoForAGPCartPath(slot))));
+ SignalBlocking(m_gci_paths[slot])
+ ->setText(QString::fromStdString(Config::GetGCIFolderPath(slot, std::nullopt)));
}
#ifdef HAS_LIBMGBA
diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.h b/Source/Core/DolphinQt/Settings/GameCubePane.h
index 4ed1934e1c..fd3dd556fa 100644
--- a/Source/Core/DolphinQt/Settings/GameCubePane.h
+++ b/Source/Core/DolphinQt/Settings/GameCubePane.h
@@ -42,6 +42,8 @@ private:
void BrowseMemcard(ExpansionInterface::Slot slot);
bool SetMemcard(ExpansionInterface::Slot slot, const QString& filename);
+ void BrowseGCIFolder(ExpansionInterface::Slot slot);
+ bool SetGCIFolder(ExpansionInterface::Slot slot, const QString& path);
void BrowseAGPRom(ExpansionInterface::Slot slot);
void SetAGPRom(ExpansionInterface::Slot slot, const QString& filename);
void BrowseGBABios();
@@ -63,6 +65,10 @@ private:
Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_agp_path_labels;
Common::EnumMap<QLineEdit*, ExpansionInterface::MAX_MEMCARD_SLOT> m_agp_paths;
+ Common::EnumMap<QHBoxLayout*, ExpansionInterface::MAX_MEMCARD_SLOT> m_gci_path_layouts;
+ Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_gci_path_labels;
+ Common::EnumMap<QLineEdit*, ExpansionInterface::MAX_MEMCARD_SLOT> m_gci_paths;
+
QCheckBox* m_gba_threads;
QCheckBox* m_gba_save_rom_path;
QPushButton* m_gba_browse_bios;