Merge pull request #11236 from AdmiralCurtiss/gci-path-config
Add config setting for base GCI folder path.
This commit is contained in:
commit
132bf6550f
|
@ -28,9 +28,11 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Dirs in both User and Sys
|
// Dirs in both User and Sys
|
||||||
|
// Legacy setups used /JAP/ while newer setups use /JPN/ by default.
|
||||||
#define EUR_DIR "EUR"
|
#define EUR_DIR "EUR"
|
||||||
#define USA_DIR "USA"
|
#define USA_DIR "USA"
|
||||||
#define JAP_DIR "JAP"
|
#define JAP_DIR "JAP"
|
||||||
|
#define JPN_DIR "JPN"
|
||||||
|
|
||||||
// Subdirs in the User dir returned by GetUserPath(D_USER_IDX)
|
// Subdirs in the User dir returned by GetUserPath(D_USER_IDX)
|
||||||
#define GC_USER_DIR "GC"
|
#define GC_USER_DIR "GC"
|
||||||
|
|
|
@ -76,6 +76,18 @@ const Info<std::string>& GetInfoForAGPCartPath(ExpansionInterface::Slot slot)
|
||||||
};
|
};
|
||||||
return *infos[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{
|
const Info<std::string> MAIN_GCI_FOLDER_A_PATH_OVERRIDE{
|
||||||
{System::Main, "Core", "GCIFolderAPathOverride"}, ""};
|
{System::Main, "Core", "GCIFolderAPathOverride"}, ""};
|
||||||
const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE{
|
const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE{
|
||||||
|
@ -549,7 +561,7 @@ DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
|
||||||
return DiscIO::Region::NTSC_J;
|
return DiscIO::Region::NTSC_J;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* GetDirectoryForRegion(DiscIO::Region region)
|
const char* GetDirectoryForRegion(DiscIO::Region region, RegionDirectoryStyle style)
|
||||||
{
|
{
|
||||||
if (region == DiscIO::Region::Unknown)
|
if (region == DiscIO::Region::Unknown)
|
||||||
region = ToGameCubeRegion(Config::Get(Config::MAIN_FALLBACK_REGION));
|
region = ToGameCubeRegion(Config::Get(Config::MAIN_FALLBACK_REGION));
|
||||||
|
@ -557,7 +569,7 @@ const char* GetDirectoryForRegion(DiscIO::Region region)
|
||||||
switch (region)
|
switch (region)
|
||||||
{
|
{
|
||||||
case DiscIO::Region::NTSC_J:
|
case DiscIO::Region::NTSC_J:
|
||||||
return JAP_DIR;
|
return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR;
|
||||||
|
|
||||||
case DiscIO::Region::NTSC_U:
|
case DiscIO::Region::NTSC_U:
|
||||||
return USA_DIR;
|
return USA_DIR;
|
||||||
|
@ -566,8 +578,9 @@ const char* GetDirectoryForRegion(DiscIO::Region region)
|
||||||
return EUR_DIR;
|
return EUR_DIR;
|
||||||
|
|
||||||
case DiscIO::Region::NTSC_K:
|
case DiscIO::Region::NTSC_K:
|
||||||
|
// See ToGameCubeRegion
|
||||||
ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region");
|
ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region");
|
||||||
return JAP_DIR; // See ToGameCubeRegion
|
return style == RegionDirectoryStyle::Legacy ? JAP_DIR : JPN_DIR;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ASSERT_MSG(BOOT, false, "Default case should not be reached");
|
ASSERT_MSG(BOOT, false, "Default case should not be reached");
|
||||||
|
@ -650,4 +663,64 @@ bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot)
|
||||||
{
|
{
|
||||||
return Config::Get(GetInfoForMemcardPath(slot)).empty();
|
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 = "/" JPN_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),
|
||||||
|
Config::RegionDirectoryStyle::Modern));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDefaultGCIFolderPathConfigured(ExpansionInterface::Slot slot)
|
||||||
|
{
|
||||||
|
return Config::Get(GetInfoForGCIPath(slot)).empty();
|
||||||
|
}
|
||||||
} // namespace Config
|
} // namespace Config
|
||||||
|
|
|
@ -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_A_PATH;
|
||||||
extern const Info<std::string> MAIN_AGP_CART_B_PATH;
|
extern const Info<std::string> MAIN_AGP_CART_B_PATH;
|
||||||
const Info<std::string>& GetInfoForAGPCartPath(ExpansionInterface::Slot slot);
|
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_A_PATH_OVERRIDE;
|
||||||
extern const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE;
|
extern const Info<std::string> MAIN_GCI_FOLDER_B_PATH_OVERRIDE;
|
||||||
const Info<std::string>& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot);
|
const Info<std::string>& GetInfoForGCIPathOverride(ExpansionInterface::Slot slot);
|
||||||
|
@ -341,8 +344,16 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
|
||||||
|
|
||||||
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
|
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
|
||||||
DiscIO::Region ToGameCubeRegion(DiscIO::Region region);
|
DiscIO::Region ToGameCubeRegion(DiscIO::Region region);
|
||||||
|
|
||||||
// The region argument must be valid for GameCube (i.e. must not be NTSC-K)
|
// The region argument must be valid for GameCube (i.e. must not be NTSC-K)
|
||||||
const char* GetDirectoryForRegion(DiscIO::Region region);
|
enum class RegionDirectoryStyle
|
||||||
|
{
|
||||||
|
Legacy,
|
||||||
|
Modern,
|
||||||
|
};
|
||||||
|
const char* GetDirectoryForRegion(DiscIO::Region region,
|
||||||
|
RegionDirectoryStyle style = RegionDirectoryStyle::Legacy);
|
||||||
|
|
||||||
std::string GetBootROMPath(const std::string& region_directory);
|
std::string GetBootROMPath(const std::string& region_directory);
|
||||||
// Builds the memory card according to the configuration with the given region and size. If the
|
// Builds the memory card according to the configuration with the given region and size. If the
|
||||||
// given region is std::nullopt, the region in the configured path is used if there is one, or the
|
// given region is std::nullopt, the region in the configured path is used if there is one, or the
|
||||||
|
@ -352,4 +363,8 @@ std::string GetMemcardPath(ExpansionInterface::Slot slot, std::optional<DiscIO::
|
||||||
std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot,
|
std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot,
|
||||||
std::optional<DiscIO::Region> region, u16 size_mb = 0x80);
|
std::optional<DiscIO::Region> region, u16 size_mb = 0x80);
|
||||||
bool IsDefaultMemcardPathConfigured(ExpansionInterface::Slot slot);
|
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
|
} // namespace Config
|
||||||
|
|
|
@ -131,6 +131,8 @@ bool IsSettingSaveable(const Config::Location& config_location)
|
||||||
&Config::MAIN_SYNC_GPU_MIN_DISTANCE.GetLocation(),
|
&Config::MAIN_SYNC_GPU_MIN_DISTANCE.GetLocation(),
|
||||||
&Config::MAIN_SYNC_GPU_OVERCLOCK.GetLocation(),
|
&Config::MAIN_SYNC_GPU_OVERCLOCK.GetLocation(),
|
||||||
&Config::MAIN_OVERRIDE_BOOT_IOS.GetLocation(),
|
&Config::MAIN_OVERRIDE_BOOT_IOS.GetLocation(),
|
||||||
|
&Config::MAIN_GCI_FOLDER_A_PATH.GetLocation(),
|
||||||
|
&Config::MAIN_GCI_FOLDER_B_PATH.GetLocation(),
|
||||||
|
|
||||||
// UI.General
|
// UI.General
|
||||||
|
|
||||||
|
|
|
@ -154,20 +154,20 @@ CEXIMemoryCard::GetGCIFolderPath(Slot card_slot, AllowMovieFolder allow_movie_fo
|
||||||
if (!path_override.empty())
|
if (!path_override.empty())
|
||||||
return {std::move(path_override), false};
|
return {std::move(path_override), false};
|
||||||
|
|
||||||
std::string path = File::GetUserPath(D_GCUSER_IDX);
|
|
||||||
|
|
||||||
const bool use_movie_folder = allow_movie_folder == AllowMovieFolder::Yes &&
|
const bool use_movie_folder = allow_movie_folder == AllowMovieFolder::Yes &&
|
||||||
Movie::IsPlayingInput() && Movie::IsConfigSaved() &&
|
Movie::IsPlayingInput() && Movie::IsConfigSaved() &&
|
||||||
Movie::IsUsingMemcard(card_slot) &&
|
Movie::IsUsingMemcard(card_slot) &&
|
||||||
Movie::IsStartingFromClearSave();
|
Movie::IsStartingFromClearSave();
|
||||||
|
|
||||||
if (use_movie_folder)
|
|
||||||
path += "Movie" DIR_SEP;
|
|
||||||
|
|
||||||
const DiscIO::Region region = Config::ToGameCubeRegion(SConfig::GetInstance().m_region);
|
const DiscIO::Region region = Config::ToGameCubeRegion(SConfig::GetInstance().m_region);
|
||||||
path = path + Config::GetDirectoryForRegion(region) + DIR_SEP +
|
if (use_movie_folder)
|
||||||
fmt::format("Card {}", s_card_short_names[card_slot]);
|
{
|
||||||
return {std::move(path), !use_movie_folder};
|
return {fmt::format("{}{}/Movie/Card {}", File::GetUserPath(D_GCUSER_IDX),
|
||||||
|
Config::GetDirectoryForRegion(region), s_card_short_names[card_slot]),
|
||||||
|
false};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {Config::GetGCIFolderPath(card_slot, region), true};
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
|
void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
|
||||||
|
|
|
@ -1686,7 +1686,8 @@ bool NetPlayServer::SyncSaveData(const SaveSyncInfo& sync_info)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const auto game_region = sync_info.game->GetRegion();
|
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)
|
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)) ==
|
else if (Config::Get(Config::GetInfoForEXIDevice(slot)) ==
|
||||||
ExpansionInterface::EXIDeviceType::MemoryCardFolder)
|
ExpansionInterface::EXIDeviceType::MemoryCardFolder)
|
||||||
{
|
{
|
||||||
const std::string path = File::GetUserPath(D_GCUSER_IDX) + region + DIR_SEP +
|
const std::string path = Config::GetGCIFolderPath(slot, gamecube_region);
|
||||||
fmt::format("Card {}", is_slot_a ? 'A' : 'B');
|
|
||||||
|
|
||||||
sf::Packet pac;
|
sf::Packet pac;
|
||||||
pac << MessageID::SyncSaveData;
|
pac << MessageID::SyncSaveData;
|
||||||
|
|
|
@ -716,16 +716,10 @@ void GameList::OpenGCSaveFolder()
|
||||||
{
|
{
|
||||||
case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
|
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));
|
std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot));
|
||||||
|
QDir dir(QString::fromStdString(override_path.empty() ?
|
||||||
if (!override_path.empty())
|
Config::GetGCIFolderPath(slot, game->GetRegion()) :
|
||||||
path = override_path;
|
override_path));
|
||||||
|
|
||||||
QDir dir(QString::fromStdString(path));
|
|
||||||
|
|
||||||
if (!dir.entryList({QStringLiteral("%1-%2-*.gci")
|
if (!dir.entryList({QStringLiteral("%1-%2-*.gci")
|
||||||
.arg(QString::fromStdString(game->GetMakerID()))
|
.arg(QString::fromStdString(game->GetMakerID()))
|
||||||
|
|
|
@ -114,6 +114,20 @@ void GameCubePane::CreateWidgets()
|
||||||
m_agp_paths[slot] = new QLineEdit();
|
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_path_labels[slot]);
|
||||||
m_agp_path_layouts[slot]->addWidget(m_agp_paths[slot]);
|
m_agp_path_layouts[slot]->addWidget(m_agp_paths[slot]);
|
||||||
|
|
||||||
|
m_gci_path_layouts[slot] = new QVBoxLayout();
|
||||||
|
m_gci_path_labels[slot] = new QLabel(tr("GCI Folder Path:"));
|
||||||
|
m_gci_override_labels[slot] =
|
||||||
|
new QLabel(tr("Warning: A GCI folder override path is currently configured for this slot. "
|
||||||
|
"Adjusting the GCI path here will have no effect."));
|
||||||
|
m_gci_override_labels[slot]->setHidden(true);
|
||||||
|
m_gci_override_labels[slot]->setWordWrap(true);
|
||||||
|
m_gci_paths[slot] = new QLineEdit();
|
||||||
|
auto* hlayout = new QHBoxLayout();
|
||||||
|
hlayout->addWidget(m_gci_path_labels[slot]);
|
||||||
|
hlayout->addWidget(m_gci_paths[slot]);
|
||||||
|
m_gci_path_layouts[slot]->addWidget(m_gci_override_labels[slot]);
|
||||||
|
m_gci_path_layouts[slot]->addLayout(hlayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add slot devices
|
// Add slot devices
|
||||||
|
@ -155,6 +169,9 @@ void GameCubePane::CreateWidgets()
|
||||||
++row;
|
++row;
|
||||||
device_layout->addLayout(m_agp_path_layouts[ExpansionInterface::Slot::A], row, 0, 1, 3);
|
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;
|
++row;
|
||||||
device_layout->addWidget(new QLabel(tr("Slot B:")), row, 0);
|
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_combos[ExpansionInterface::Slot::B], row, 1);
|
||||||
|
@ -166,6 +183,9 @@ void GameCubePane::CreateWidgets()
|
||||||
++row;
|
++row;
|
||||||
device_layout->addLayout(m_agp_path_layouts[ExpansionInterface::Slot::B], row, 0, 1, 3);
|
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;
|
++row;
|
||||||
device_layout->addWidget(new QLabel(tr("SP1:")), row, 0);
|
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_combos[ExpansionInterface::Slot::SP1], row, 1);
|
||||||
|
@ -249,6 +269,11 @@ void GameCubePane::ConnectWidgets()
|
||||||
});
|
});
|
||||||
connect(m_agp_paths[slot], &QLineEdit::editingFinished,
|
connect(m_agp_paths[slot], &QLineEdit::editingFinished,
|
||||||
[this, slot] { SetAGPRom(slot, m_agp_paths[slot]->text()); });
|
[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
|
#ifdef HAS_LIBMGBA
|
||||||
|
@ -302,14 +327,28 @@ void GameCubePane::UpdateButton(ExpansionInterface::Slot slot)
|
||||||
case ExpansionInterface::Slot::B:
|
case ExpansionInterface::Slot::B:
|
||||||
{
|
{
|
||||||
has_config = (device == ExpansionInterface::EXIDeviceType::MemoryCard ||
|
has_config = (device == ExpansionInterface::EXIDeviceType::MemoryCard ||
|
||||||
|
device == ExpansionInterface::EXIDeviceType::MemoryCardFolder ||
|
||||||
device == ExpansionInterface::EXIDeviceType::AGP ||
|
device == ExpansionInterface::EXIDeviceType::AGP ||
|
||||||
device == ExpansionInterface::EXIDeviceType::Microphone);
|
device == ExpansionInterface::EXIDeviceType::Microphone);
|
||||||
const bool hide_memory_card = device != ExpansionInterface::EXIDeviceType::MemoryCard ||
|
const bool hide_memory_card = device != ExpansionInterface::EXIDeviceType::MemoryCard ||
|
||||||
Config::IsDefaultMemcardPathConfigured(slot);
|
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_path_labels[slot]->setHidden(hide_memory_card);
|
||||||
m_memcard_paths[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_path_labels[slot]->setHidden(device != ExpansionInterface::EXIDeviceType::AGP);
|
||||||
m_agp_paths[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);
|
||||||
|
|
||||||
|
// In the years before we introduced the GCI folder configuration paths it has become somewhat
|
||||||
|
// popular to use the GCI override path instead. Check if this is the case and display a warning
|
||||||
|
// if it is set, so users aren't confused why configuring the normal GCI path doesn't do
|
||||||
|
// anything.
|
||||||
|
m_gci_override_labels[slot]->setHidden(
|
||||||
|
device != ExpansionInterface::EXIDeviceType::MemoryCardFolder ||
|
||||||
|
Config::Get(Config::GetInfoForGCIPathOverride(slot)).empty());
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExpansionInterface::Slot::SP1:
|
case ExpansionInterface::Slot::SP1:
|
||||||
|
@ -332,6 +371,9 @@ void GameCubePane::OnConfigPressed(ExpansionInterface::Slot slot)
|
||||||
case ExpansionInterface::EXIDeviceType::MemoryCard:
|
case ExpansionInterface::EXIDeviceType::MemoryCard:
|
||||||
BrowseMemcard(slot);
|
BrowseMemcard(slot);
|
||||||
return;
|
return;
|
||||||
|
case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
|
||||||
|
BrowseGCIFolder(slot);
|
||||||
|
return;
|
||||||
case ExpansionInterface::EXIDeviceType::AGP:
|
case ExpansionInterface::EXIDeviceType::AGP:
|
||||||
BrowseAGPRom(slot);
|
BrowseAGPRom(slot);
|
||||||
return;
|
return;
|
||||||
|
@ -462,6 +504,111 @@ bool GameCubePane::SetMemcard(ExpansionInterface::Slot slot, const QString& file
|
||||||
return true;
|
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)
|
void GameCubePane::BrowseAGPRom(ExpansionInterface::Slot slot)
|
||||||
{
|
{
|
||||||
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
|
ASSERT(ExpansionInterface::IsMemcardSlot(slot));
|
||||||
|
@ -576,6 +723,8 @@ void GameCubePane::LoadSettings()
|
||||||
->setText(QString::fromStdString(Config::GetMemcardPath(slot, std::nullopt)));
|
->setText(QString::fromStdString(Config::GetMemcardPath(slot, std::nullopt)));
|
||||||
SignalBlocking(m_agp_paths[slot])
|
SignalBlocking(m_agp_paths[slot])
|
||||||
->setText(QString::fromStdString(Config::Get(Config::GetInfoForAGPCartPath(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
|
#ifdef HAS_LIBMGBA
|
||||||
|
|
|
@ -19,6 +19,7 @@ class QLabel;
|
||||||
class QLineEdit;
|
class QLineEdit;
|
||||||
class QPushButton;
|
class QPushButton;
|
||||||
class QString;
|
class QString;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
class GameCubePane : public QWidget
|
class GameCubePane : public QWidget
|
||||||
{
|
{
|
||||||
|
@ -42,6 +43,8 @@ private:
|
||||||
|
|
||||||
void BrowseMemcard(ExpansionInterface::Slot slot);
|
void BrowseMemcard(ExpansionInterface::Slot slot);
|
||||||
bool SetMemcard(ExpansionInterface::Slot slot, const QString& filename);
|
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 BrowseAGPRom(ExpansionInterface::Slot slot);
|
||||||
void SetAGPRom(ExpansionInterface::Slot slot, const QString& filename);
|
void SetAGPRom(ExpansionInterface::Slot slot, const QString& filename);
|
||||||
void BrowseGBABios();
|
void BrowseGBABios();
|
||||||
|
@ -63,6 +66,11 @@ private:
|
||||||
Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_agp_path_labels;
|
Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_agp_path_labels;
|
||||||
Common::EnumMap<QLineEdit*, ExpansionInterface::MAX_MEMCARD_SLOT> m_agp_paths;
|
Common::EnumMap<QLineEdit*, ExpansionInterface::MAX_MEMCARD_SLOT> m_agp_paths;
|
||||||
|
|
||||||
|
Common::EnumMap<QVBoxLayout*, ExpansionInterface::MAX_MEMCARD_SLOT> m_gci_path_layouts;
|
||||||
|
Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_gci_path_labels;
|
||||||
|
Common::EnumMap<QLabel*, ExpansionInterface::MAX_MEMCARD_SLOT> m_gci_override_labels;
|
||||||
|
Common::EnumMap<QLineEdit*, ExpansionInterface::MAX_MEMCARD_SLOT> m_gci_paths;
|
||||||
|
|
||||||
QCheckBox* m_gba_threads;
|
QCheckBox* m_gba_threads;
|
||||||
QCheckBox* m_gba_save_rom_path;
|
QCheckBox* m_gba_save_rom_path;
|
||||||
QPushButton* m_gba_browse_bios;
|
QPushButton* m_gba_browse_bios;
|
||||||
|
|
Loading…
Reference in New Issue