Merge pull request #10587 from AdmiralCurtiss/memcard-path-unify

Raw Memory Card Path handling unification and consistency fixes.
This commit is contained in:
JMC47 2022-06-13 20:18:36 -04:00 committed by GitHub
commit 9315ac7071
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 236 additions and 199 deletions

View File

@ -335,6 +335,23 @@ bool SplitPath(std::string_view full_path, std::string* path, std::string* filen
return true; return true;
} }
void UnifyPathSeparators(std::string& path)
{
#ifdef _WIN32
for (char& c : path)
{
if (c == '\\')
c = '/';
}
#endif
}
std::string WithUnifiedPathSeparators(std::string path)
{
UnifyPathSeparators(path);
return path;
}
std::string PathToFileName(std::string_view path) std::string PathToFileName(std::string_view path)
{ {
std::string file_name, extension; std::string file_name, extension;

View File

@ -157,9 +157,17 @@ std::vector<std::string> SplitString(const std::string& str, char delim);
std::string JoinStrings(const std::vector<std::string>& strings, const std::string& delimiter); std::string JoinStrings(const std::vector<std::string>& strings, const std::string& delimiter);
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe" // "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
// This requires forward slashes to be used for the path separators, even on Windows.
bool SplitPath(std::string_view full_path, std::string* path, std::string* filename, bool SplitPath(std::string_view full_path, std::string* path, std::string* filename,
std::string* extension); std::string* extension);
// Converts the path separators of a path into forward slashes on Windows, which is assumed to be
// true for paths at various places in the codebase.
void UnifyPathSeparators(std::string& path);
std::string WithUnifiedPathSeparators(std::string path);
// Extracts just the filename (including extension) from a full path.
// This requires forward slashes to be used for the path separators, even on Windows.
std::string PathToFileName(std::string_view path); std::string PathToFileName(std::string_view path);
bool StringBeginsWith(std::string_view str, std::string_view begin); bool StringBeginsWith(std::string_view str, std::string_view begin);

View File

@ -311,8 +311,8 @@ std::unique_ptr<BootParameters> BootParameters::GenerateFromFile(std::vector<std
BootParameters::IPL::IPL(DiscIO::Region region_) : region(region_) BootParameters::IPL::IPL(DiscIO::Region region_) : region(region_)
{ {
const std::string directory = SConfig::GetInstance().GetDirectoryForRegion(region); const std::string directory = Config::GetDirectoryForRegion(region);
path = SConfig::GetInstance().GetBootROMPath(directory); path = Config::GetBootROMPath(directory);
} }
BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_) BootParameters::IPL::IPL(DiscIO::Region region_, Disc&& disc_) : IPL(region_)
@ -434,7 +434,7 @@ bool CBoot::Load_BS2(const std::string& boot_rom_filename)
if (known_ipl && pal_ipl != (boot_region == DiscIO::Region::PAL)) if (known_ipl && pal_ipl != (boot_region == DiscIO::Region::PAL))
{ {
PanicAlertFmtT("{0} IPL found in {1} directory. The disc might not be recognized", PanicAlertFmtT("{0} IPL found in {1} directory. The disc might not be recognized",
pal_ipl ? "PAL" : "NTSC", SConfig::GetDirectoryForRegion(boot_region)); pal_ipl ? "PAL" : "NTSC", Config::GetDirectoryForRegion(boot_region));
} }
// Run the descrambler over the encrypted section containing BS1/BS2 // Run the descrambler over the encrypted section containing BS1/BS2

View File

@ -12,6 +12,7 @@
#include "Common/CommonPaths.h" #include "Common/CommonPaths.h"
#include "Common/Config/Config.h" #include "Common/Config/Config.h"
#include "Common/EnumMap.h" #include "Common/EnumMap.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Common/MathUtil.h" #include "Common/MathUtil.h"
#include "Common/StringUtil.h" #include "Common/StringUtil.h"
@ -19,6 +20,7 @@
#include "Core/Config/DefaultLocale.h" #include "Core/Config/DefaultLocale.h"
#include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI.h"
#include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/EXI/EXI_Device.h"
#include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/HSP/HSP_Device.h" #include "Core/HW/HSP/HSP_Device.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "Core/HW/SI/SI_Device.h" #include "Core/HW/SI/SI_Device.h"
@ -523,4 +525,97 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices)
{ {
Config::SetBase(Config::MAIN_USB_PASSTHROUGH_DEVICES, SaveUSBWhitelistToString(devices)); Config::SetBase(Config::MAIN_USB_PASSTHROUGH_DEVICES, SaveUSBWhitelistToString(devices));
} }
// The reason we need this function is because some memory card code
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
DiscIO::Region ToGameCubeRegion(DiscIO::Region region)
{
if (region != DiscIO::Region::NTSC_K)
return region;
// GameCube has no NTSC-K region. No choice of replacement value is completely
// non-arbitrary, but let's go with NTSC-J since Korean GameCubes are NTSC-J.
return DiscIO::Region::NTSC_J;
}
const char* GetDirectoryForRegion(DiscIO::Region region)
{
if (region == DiscIO::Region::Unknown)
region = ToGameCubeRegion(Config::Get(Config::MAIN_FALLBACK_REGION));
switch (region)
{
case DiscIO::Region::NTSC_J:
return JAP_DIR;
case DiscIO::Region::NTSC_U:
return USA_DIR;
case DiscIO::Region::PAL:
return EUR_DIR;
case DiscIO::Region::NTSC_K:
ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region");
return JAP_DIR; // See ToGameCubeRegion
default:
ASSERT_MSG(BOOT, false, "Default case should not be reached");
return EUR_DIR;
}
}
std::string GetBootROMPath(const std::string& region_directory)
{
const std::string path =
File::GetUserPath(D_GCUSER_IDX) + DIR_SEP + region_directory + DIR_SEP GC_IPL;
if (!File::Exists(path))
return File::GetSysDirectory() + GC_SYS_DIR + DIR_SEP + region_directory + DIR_SEP GC_IPL;
return path;
}
std::string GetMemcardPath(ExpansionInterface::Slot slot, DiscIO::Region region, u16 size_mb)
{
return GetMemcardPath(Config::Get(GetInfoForMemcardPath(slot)), slot, region, size_mb);
}
std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot,
DiscIO::Region region, u16 size_mb)
{
const std::string region_dir = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(region));
const std::string blocks_string = size_mb < Memcard::MBIT_SIZE_MEMORY_CARD_2043 ?
fmt::format(".{}", Memcard::MbitToFreeBlocks(size_mb)) :
"";
if (configured_filename.empty())
{
// Use default memcard path if there is no user defined one.
const bool is_slot_a = slot == ExpansionInterface::Slot::A;
return fmt::format("{}{}.{}{}.raw", File::GetUserPath(D_GCUSER_IDX),
is_slot_a ? GC_MEMCARDA : GC_MEMCARDB, region_dir, blocks_string);
}
// Custom path is expected to be stored in the form of
// "/path/to/file.{region_code}.raw"
// with an arbitrary but supported region code.
// Try to extract and replace that region code.
// If there's no region code just insert one before the extension.
std::string dir;
std::string name;
std::string ext;
UnifyPathSeparators(configured_filename);
SplitPath(configured_filename, &dir, &name, &ext);
constexpr std::string_view us_region = "." USA_DIR;
constexpr std::string_view jp_region = "." JAP_DIR;
constexpr std::string_view eu_region = "." EUR_DIR;
if (StringEndsWith(name, us_region))
name = name.substr(0, name.size() - us_region.size());
else if (StringEndsWith(name, jp_region))
name = name.substr(0, name.size() - jp_region.size());
else if (StringEndsWith(name, eu_region))
name = name.substr(0, name.size() - eu_region.size());
return fmt::format("{}{}.{}{}{}", dir, name, region_dir, blocks_string, ext);
}
} // namespace Config } // namespace Config

View File

@ -331,4 +331,15 @@ extern const Info<std::string> MAIN_USB_PASSTHROUGH_DEVICES;
std::set<std::pair<u16, u16>> GetUSBDeviceWhitelist(); std::set<std::pair<u16, u16>> GetUSBDeviceWhitelist();
void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices); void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
// GameCube path utility functions
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
DiscIO::Region ToGameCubeRegion(DiscIO::Region region);
// The region argument must be valid for GameCube (i.e. must not be NTSC-K)
const char* GetDirectoryForRegion(DiscIO::Region region);
std::string GetBootROMPath(const std::string& region_directory);
std::string GetMemcardPath(ExpansionInterface::Slot slot, DiscIO::Region region,
u16 size_mb = 0x80);
std::string GetMemcardPath(std::string configured_filename, ExpansionInterface::Slot slot,
DiscIO::Region region, u16 size_mb = 0x80);
} // namespace Config } // namespace Config

View File

@ -220,53 +220,6 @@ std::string SConfig::MakeGameID(std::string_view file_name)
return "ID-" + std::string(file_name.substr(0, lastdot)); return "ID-" + std::string(file_name.substr(0, lastdot));
} }
// The reason we need this function is because some memory card code
// expects to get a non-NTSC-K region even if we're emulating an NTSC-K Wii.
DiscIO::Region SConfig::ToGameCubeRegion(DiscIO::Region region)
{
if (region != DiscIO::Region::NTSC_K)
return region;
// GameCube has no NTSC-K region. No choice of replacement value is completely
// non-arbitrary, but let's go with NTSC-J since Korean GameCubes are NTSC-J.
return DiscIO::Region::NTSC_J;
}
const char* SConfig::GetDirectoryForRegion(DiscIO::Region region)
{
if (region == DiscIO::Region::Unknown)
region = ToGameCubeRegion(GetFallbackRegion());
switch (region)
{
case DiscIO::Region::NTSC_J:
return JAP_DIR;
case DiscIO::Region::NTSC_U:
return USA_DIR;
case DiscIO::Region::PAL:
return EUR_DIR;
case DiscIO::Region::NTSC_K:
ASSERT_MSG(BOOT, false, "NTSC-K is not a valid GameCube region");
return JAP_DIR; // See ToGameCubeRegion
default:
ASSERT_MSG(BOOT, false, "Default case should not be reached");
return EUR_DIR;
}
}
std::string SConfig::GetBootROMPath(const std::string& region_directory) const
{
const std::string path =
File::GetUserPath(D_GCUSER_IDX) + DIR_SEP + region_directory + DIR_SEP GC_IPL;
if (!File::Exists(path))
return File::GetSysDirectory() + GC_SYS_DIR + DIR_SEP + region_directory + DIR_SEP GC_IPL;
return path;
}
struct SetGameMetadata struct SetGameMetadata
{ {
SetGameMetadata(SConfig* config_, DiscIO::Region* region_) : config(config_), region(region_) {} SetGameMetadata(SConfig* config_, DiscIO::Region* region_) : config(config_), region(region_) {}
@ -375,21 +328,16 @@ bool SConfig::SetPathsAndGameMetadata(const BootParameters& boot)
return false; return false;
if (m_region == DiscIO::Region::Unknown) if (m_region == DiscIO::Region::Unknown)
m_region = GetFallbackRegion(); m_region = Config::Get(Config::MAIN_FALLBACK_REGION);
// Set up paths // Set up paths
const std::string region_dir = GetDirectoryForRegion(ToGameCubeRegion(m_region)); const std::string region_dir = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(m_region));
m_strSRAM = File::GetUserPath(F_GCSRAM_IDX); m_strSRAM = File::GetUserPath(F_GCSRAM_IDX);
m_strBootROM = GetBootROMPath(region_dir); m_strBootROM = Config::GetBootROMPath(region_dir);
return true; return true;
} }
DiscIO::Region SConfig::GetFallbackRegion()
{
return Config::Get(Config::MAIN_FALLBACK_REGION);
}
DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const
{ {
DiscIO::Language language; DiscIO::Language language;

View File

@ -72,13 +72,7 @@ struct SConfig
void LoadDefaults(); void LoadDefaults();
static std::string MakeGameID(std::string_view file_name); static std::string MakeGameID(std::string_view file_name);
// Replaces NTSC-K with some other region, and doesn't replace non-NTSC-K regions
static DiscIO::Region ToGameCubeRegion(DiscIO::Region region);
// The region argument must be valid for GameCube (i.e. must not be NTSC-K)
static const char* GetDirectoryForRegion(DiscIO::Region region);
std::string GetBootROMPath(const std::string& region_directory) const;
bool SetPathsAndGameMetadata(const BootParameters& boot); bool SetPathsAndGameMetadata(const BootParameters& boot);
static DiscIO::Region GetFallbackRegion();
DiscIO::Language GetCurrentLanguage(bool wii) const; DiscIO::Language GetCurrentLanguage(bool wii) const;
DiscIO::Language GetLanguageAdjustedForRegion(bool wii, DiscIO::Region region) const; DiscIO::Language GetLanguageAdjustedForRegion(bool wii, DiscIO::Region region) const;

View File

@ -117,7 +117,7 @@ void Init()
if (size_override >= 0 && size_override <= 4) if (size_override >= 0 && size_override <= 4)
size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_59 << size_override; size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_59 << size_override;
const bool shift_jis = const bool shift_jis =
SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region) == DiscIO::Region::NTSC_J; Config::ToGameCubeRegion(SConfig::GetInstance().m_region) == DiscIO::Region::NTSC_J;
const CardFlashId& flash_id = g_SRAM.settings_ex.flash_id[Memcard::SLOT_A]; const CardFlashId& flash_id = g_SRAM.settings_ex.flash_id[Memcard::SLOT_A];
const u32 rtc_bias = g_SRAM.settings.rtc_bias; const u32 rtc_bias = g_SRAM.settings.rtc_bias;
const u32 sram_language = static_cast<u32>(g_SRAM.settings.language); const u32 sram_language = static_cast<u32>(g_SRAM.settings.language);

View File

@ -162,8 +162,8 @@ CEXIMemoryCard::GetGCIFolderPath(Slot card_slot, AllowMovieFolder allow_movie_fo
if (use_movie_folder) if (use_movie_folder)
path += "Movie" DIR_SEP; path += "Movie" DIR_SEP;
const DiscIO::Region region = SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region); const DiscIO::Region region = Config::ToGameCubeRegion(SConfig::GetInstance().m_region);
path = path + SConfig::GetDirectoryForRegion(region) + DIR_SEP + path = path + Config::GetDirectoryForRegion(region) + DIR_SEP +
fmt::format("Card {}", s_card_short_names[card_slot]); fmt::format("Card {}", s_card_short_names[card_slot]);
return {std::move(path), !use_movie_folder}; return {std::move(path), !use_movie_folder};
} }
@ -188,7 +188,7 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
if (!file_info.Exists()) if (!file_info.Exists())
{ {
if (migrate) // first use of memcard folder, migrate automatically if (migrate) // first use of memcard folder, migrate automatically
MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot); MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot, SConfig::GetInstance().m_region);
else else
File::CreateFullPath(dir_path + DIR_SEP); File::CreateFullPath(dir_path + DIR_SEP);
} }
@ -198,7 +198,7 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
{ {
PanicAlertFmtT("{0} was not a directory, moved to *.original", dir_path); PanicAlertFmtT("{0} was not a directory, moved to *.original", dir_path);
if (migrate) if (migrate)
MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot); MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot, SConfig::GetInstance().m_region);
else else
File::CreateFullPath(dir_path + DIR_SEP); File::CreateFullPath(dir_path + DIR_SEP);
} }
@ -218,22 +218,16 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
void CEXIMemoryCard::SetupRawMemcard(u16 size_mb) void CEXIMemoryCard::SetupRawMemcard(u16 size_mb)
{ {
std::string filename = Config::Get(Config::GetInfoForMemcardPath(m_card_slot)); std::string filename;
if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(m_card_slot) && if (Movie::IsPlayingInput() && Movie::IsConfigSaved() && Movie::IsUsingMemcard(m_card_slot) &&
Movie::IsStartingFromClearSave()) Movie::IsStartingFromClearSave())
{ {
filename = File::GetUserPath(D_GCUSER_IDX) + filename = File::GetUserPath(D_GCUSER_IDX) +
fmt::format("Movie{}.raw", s_card_short_names[m_card_slot]); fmt::format("Movie{}.raw", s_card_short_names[m_card_slot]);
} }
else
const std::string region_dir =
SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region));
MemoryCard::CheckPath(filename, region_dir, m_card_slot);
if (size_mb < Memcard::MBIT_SIZE_MEMORY_CARD_2043)
{ {
filename.insert(filename.find_last_of('.'), filename = Config::GetMemcardPath(m_card_slot, SConfig::GetInstance().m_region, size_mb);
fmt::format(".{}", Memcard::MbitToFreeBlocks(size_mb)));
} }
m_memory_card = std::make_unique<MemoryCard>(filename, m_card_slot, size_mb); m_memory_card = std::make_unique<MemoryCard>(filename, m_card_slot, size_mb);

View File

@ -705,10 +705,11 @@ void GCMemcardDirectory::DoState(PointerWrap& p)
} }
} }
void MigrateFromMemcardFile(const std::string& directory_name, ExpansionInterface::Slot card_slot) void MigrateFromMemcardFile(const std::string& directory_name, ExpansionInterface::Slot card_slot,
DiscIO::Region region)
{ {
File::CreateFullPath(directory_name); File::CreateFullPath(directory_name);
std::string ini_memcard = Config::Get(Config::GetInfoForMemcardPath(card_slot)); const std::string ini_memcard = Config::GetMemcardPath(card_slot, region);
if (File::Exists(ini_memcard)) if (File::Exists(ini_memcard))
{ {
auto [error_code, memcard] = Memcard::GCMemcard::Open(ini_memcard.c_str()); auto [error_code, memcard] = Memcard::GCMemcard::Open(ini_memcard.c_str());

View File

@ -12,10 +12,12 @@
#include "Core/HW/GCMemcard/GCIFile.h" #include "Core/HW/GCMemcard/GCIFile.h"
#include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/GCMemcard/GCMemcardBase.h" #include "Core/HW/GCMemcard/GCMemcardBase.h"
#include "DiscIO/Enums.h"
// Uncomment this to write the system data of the memorycard from directory to disc // Uncomment this to write the system data of the memorycard from directory to disc
//#define _WRITE_MC_HEADER 1 //#define _WRITE_MC_HEADER 1
void MigrateFromMemcardFile(const std::string& directory_name, ExpansionInterface::Slot card_slot); void MigrateFromMemcardFile(const std::string& directory_name, ExpansionInterface::Slot card_slot,
DiscIO::Region region);
class GCMemcardDirectory : public MemoryCardBase class GCMemcardDirectory : public MemoryCardBase
{ {

View File

@ -90,55 +90,6 @@ MemoryCard::~MemoryCard()
} }
} }
void MemoryCard::CheckPath(std::string& memcardPath, const std::string& gameRegion,
ExpansionInterface::Slot card_slot)
{
bool is_slot_a = card_slot == ExpansionInterface::Slot::A;
std::string ext("." + gameRegion + ".raw");
if (memcardPath.empty())
{
// Use default memcard path if there is no user defined name
std::string defaultFilename = is_slot_a ? GC_MEMCARDA : GC_MEMCARDB;
memcardPath = File::GetUserPath(D_GCUSER_IDX) + defaultFilename + ext;
}
else
{
std::string filename = memcardPath;
std::string region = filename.substr(filename.size() - 7, 3);
bool hasregion = false;
hasregion |= region.compare(USA_DIR) == 0;
hasregion |= region.compare(JAP_DIR) == 0;
hasregion |= region.compare(EUR_DIR) == 0;
if (!hasregion)
{
// filename doesn't have region in the extension
if (File::Exists(filename))
{
// If the old file exists we are polite and ask if we should copy it
std::string oldFilename = filename;
filename.replace(filename.size() - 4, 4, ext);
if (PanicYesNoFmtT("Memory Card filename in Slot {0} is incorrect\n"
"Region not specified\n\n"
"Slot {1} path was changed to\n"
"{2}\n"
"Would you like to copy the old file to this new location?\n",
is_slot_a ? 'A' : 'B', is_slot_a ? 'A' : 'B', filename))
{
if (!File::Copy(oldFilename, filename))
PanicAlertFmtT("Copy failed");
}
}
memcardPath = filename; // Always correct the path!
}
else if (region.compare(gameRegion) != 0)
{
// filename has region, but it's not == gameRegion
// Just set the correct filename, the EXI Device will create it if it doesn't exist
memcardPath = filename.replace(filename.size() - ext.size(), ext.size(), ext);
}
}
}
void MemoryCard::FlushThread() void MemoryCard::FlushThread()
{ {
if (!Config::Get(Config::SESSION_SAVE_DATA_WRITABLE)) if (!Config::Get(Config::SESSION_SAVE_DATA_WRITABLE))

View File

@ -20,8 +20,6 @@ public:
MemoryCard(const std::string& filename, ExpansionInterface::Slot card_slot, MemoryCard(const std::string& filename, ExpansionInterface::Slot card_slot,
u16 size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043); u16 size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043);
~MemoryCard(); ~MemoryCard();
static void CheckPath(std::string& memcardPath, const std::string& gameRegion,
ExpansionInterface::Slot slot);
void FlushThread(); void FlushThread();
void MakeDirty(); void MakeDirty();

View File

@ -1479,6 +1479,9 @@ void GetSettings()
} }
else else
{ {
const auto raw_memcard_exists = [](ExpansionInterface::Slot card_slot) {
return File::Exists(Config::GetMemcardPath(card_slot, SConfig::GetInstance().m_region));
};
const auto gci_folder_has_saves = [](ExpansionInterface::Slot card_slot) { const auto gci_folder_has_saves = [](ExpansionInterface::Slot card_slot) {
const auto [path, migrate] = ExpansionInterface::CEXIMemoryCard::GetGCIFolderPath( const auto [path, migrate] = ExpansionInterface::CEXIMemoryCard::GetGCIFolderPath(
card_slot, ExpansionInterface::AllowMovieFolder::No); card_slot, ExpansionInterface::AllowMovieFolder::No);
@ -1486,11 +1489,10 @@ void GetSettings()
return number_of_saves > 0; return number_of_saves > 0;
}; };
s_bClearSave = s_bClearSave = !(slot_a_has_raw_memcard && raw_memcard_exists(ExpansionInterface::Slot::A)) &&
!(slot_a_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_A_PATH))) && !(slot_b_has_raw_memcard && raw_memcard_exists(ExpansionInterface::Slot::B)) &&
!(slot_b_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_B_PATH))) && !(slot_a_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::A)) &&
!(slot_a_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::A)) && !(slot_b_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::B));
!(slot_b_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::B));
} }
s_memcards |= (slot_a_has_raw_memcard || slot_a_has_gci_folder) << 0; s_memcards |= (slot_a_has_raw_memcard || slot_a_has_gci_folder) << 0;
s_memcards |= (slot_b_has_raw_memcard || slot_b_has_gci_folder) << 1; s_memcards |= (slot_b_has_raw_memcard || slot_b_has_gci_folder) << 1;

View File

@ -1470,8 +1470,8 @@ bool NetPlayServer::StartGame()
const sf::Uint64 initial_rtc = GetInitialNetPlayRTC(); const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();
const std::string region = SConfig::GetDirectoryForRegion( const std::string region = Config::GetDirectoryForRegion(
SConfig::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game_identifier)->GetRegion())); Config::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game_identifier)->GetRegion()));
// sync GC SRAM with clients // sync GC SRAM with clients
if (!g_SRAM_netplay_initialized) if (!g_SRAM_netplay_initialized)
@ -1665,8 +1665,8 @@ bool NetPlayServer::SyncSaveData()
if (save_count == 0) if (save_count == 0)
return true; return true;
const std::string region = const auto game_region = game->GetRegion();
SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(game->GetRegion())); const std::string region = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(game_region));
for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS) for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{ {
@ -1674,17 +1674,12 @@ bool NetPlayServer::SyncSaveData()
if (m_settings.m_EXIDevice[slot] == ExpansionInterface::EXIDeviceType::MemoryCard) if (m_settings.m_EXIDevice[slot] == ExpansionInterface::EXIDeviceType::MemoryCard)
{ {
std::string path = Config::Get(Config::GetInfoForMemcardPath(slot));
MemoryCard::CheckPath(path, region, slot);
const int size_override = m_settings.m_MemcardSizeOverride; const int size_override = m_settings.m_MemcardSizeOverride;
if (size_override >= 0 && size_override <= 4) const u16 card_size_mbits =
{ size_override >= 0 && size_override <= 4 ?
path.insert(path.find_last_of('.'), static_cast<u16>(Memcard::MBIT_SIZE_MEMORY_CARD_59 << size_override) :
fmt::format(".{}", Memcard::MbitToFreeBlocks(Memcard::MBIT_SIZE_MEMORY_CARD_59 Memcard::MBIT_SIZE_MEMORY_CARD_2043;
<< size_override))); const std::string path = Config::GetMemcardPath(slot, game_region, card_size_mbits);
}
sf::Packet pac; sf::Packet pac;
pac << MessageID::SyncSaveData; pac << MessageID::SyncSaveData;

View File

@ -228,7 +228,8 @@ void GCMemcardManager::LoadDefaultMemcards()
continue; continue;
} }
const QString path = QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(slot))); const QString path = QString::fromStdString(
Config::GetMemcardPath(slot, Config::Get(Config::MAIN_FALLBACK_REGION)));
SetSlotFile(slot, path); SetSlotFile(slot, path);
} }
} }

View File

@ -713,7 +713,7 @@ void GameList::OpenGCSaveFolder()
case ExpansionInterface::EXIDeviceType::MemoryCardFolder: case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
{ {
std::string path = StringFromFormat("%s/%s/%s", File::GetUserPath(D_GCUSER_IDX).c_str(), std::string path = StringFromFormat("%s/%s/%s", File::GetUserPath(D_GCUSER_IDX).c_str(),
SConfig::GetDirectoryForRegion(game->GetRegion()), Config::GetDirectoryForRegion(game->GetRegion()),
slot == Slot::A ? "Card A" : "Card B"); 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));
@ -734,7 +734,7 @@ void GameList::OpenGCSaveFolder()
} }
case ExpansionInterface::EXIDeviceType::MemoryCard: case ExpansionInterface::EXIDeviceType::MemoryCard:
{ {
std::string memcard_path = Config::Get(Config::GetInfoForMemcardPath(slot)); const std::string memcard_path = Config::GetMemcardPath(slot, game->GetRegion());
std::string memcard_dir; std::string memcard_dir;

View File

@ -1001,12 +1001,9 @@ void MenuBar::UpdateToolsMenu(bool emulation_started)
{ {
m_boot_sysmenu->setEnabled(!emulation_started); m_boot_sysmenu->setEnabled(!emulation_started);
m_perform_online_update_menu->setEnabled(!emulation_started); m_perform_online_update_menu->setEnabled(!emulation_started);
m_ntscj_ipl->setEnabled(!emulation_started && m_ntscj_ipl->setEnabled(!emulation_started && File::Exists(Config::GetBootROMPath(JAP_DIR)));
File::Exists(SConfig::GetInstance().GetBootROMPath(JAP_DIR))); m_ntscu_ipl->setEnabled(!emulation_started && File::Exists(Config::GetBootROMPath(USA_DIR)));
m_ntscu_ipl->setEnabled(!emulation_started && m_pal_ipl->setEnabled(!emulation_started && File::Exists(Config::GetBootROMPath(EUR_DIR)));
File::Exists(SConfig::GetInstance().GetBootROMPath(USA_DIR)));
m_pal_ipl->setEnabled(!emulation_started &&
File::Exists(SConfig::GetInstance().GetBootROMPath(EUR_DIR)));
m_import_backup->setEnabled(!emulation_started); m_import_backup->setEnabled(!emulation_started);
m_check_nand->setEnabled(!emulation_started); m_check_nand->setEnabled(!emulation_started);

View File

@ -303,66 +303,89 @@ void GameCubePane::BrowseMemcard(ExpansionInterface::Slot slot)
{ {
ASSERT(ExpansionInterface::IsMemcardSlot(slot)); ASSERT(ExpansionInterface::IsMemcardSlot(slot));
QString filename = DolphinFileDialog::getSaveFileName( const QString filename = DolphinFileDialog::getSaveFileName(
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)), this, tr("Choose a file to open or create"),
QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
tr("GameCube Memory Cards (*.raw *.gcp)"), 0, QFileDialog::DontConfirmOverwrite); tr("GameCube Memory Cards (*.raw *.gcp)"), 0, QFileDialog::DontConfirmOverwrite);
if (filename.isEmpty()) if (filename.isEmpty())
return; return;
QString path_abs = QFileInfo(filename).absoluteFilePath(); const std::string raw_path =
WithUnifiedPathSeparators(QFileInfo(filename).absoluteFilePath().toStdString());
// Memcard validity checks // Figure out if the user selected a card that has a valid region specifier in the filename.
if (File::Exists(filename.toStdString())) const std::string jp_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::NTSC_J);
const std::string us_path = Config::GetMemcardPath(raw_path, slot, DiscIO::Region::NTSC_U);
const std::string eu_path = Config::GetMemcardPath(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)
{ {
auto [error_code, mc] = Memcard::GCMemcard::Open(filename.toStdString()); // TODO: We could try to autodetect the card region here and offer automatic renaming.
ModalMessageBox::critical(this, tr("Error"),
if (error_code.HasCriticalErrors() || !mc || !mc->IsValid()) tr("The filename %1 does not conform to Dolphin's region code format "
{ "for memory cards. Please rename this file to either %2, %3, or "
ModalMessageBox::critical( "%4, matching the region of the save files that are on it.")
this, tr("Error"), .arg(QString::fromStdString(PathToFileName(raw_path)))
tr("The file\n%1\nis either corrupted or not a GameCube memory card file.\n%2") .arg(QString::fromStdString(PathToFileName(us_path)))
.arg(filename) .arg(QString::fromStdString(PathToFileName(eu_path)))
.arg(GCMemcardManager::GetErrorMessagesForErrorCode(error_code))); .arg(QString::fromStdString(PathToFileName(jp_path))));
return; return;
}
} }
for (ExpansionInterface::Slot other_slot : ExpansionInterface::MEMCARD_SLOTS) // Memcard validity checks
for (const std::string& path : {jp_path, us_path, eu_path})
{ {
if (other_slot == slot) if (File::Exists(path))
continue;
bool other_slot_memcard = m_slot_combos[other_slot]->currentData().toInt() ==
static_cast<int>(ExpansionInterface::EXIDeviceType::MemoryCard);
if (other_slot_memcard)
{ {
QString path_other = auto [error_code, mc] = Memcard::GCMemcard::Open(path);
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(other_slot))))
.absoluteFilePath();
if (path_abs == path_other) if (error_code.HasCriticalErrors() || !mc || !mc->IsValid())
{ {
ModalMessageBox::critical( ModalMessageBox::critical(
this, tr("Error"), this, tr("Error"),
tr("The same file can't be used in multiple slots; it is already used by %1.") tr("The file\n%1\nis either corrupted or not a GameCube memory card file.\n%2")
.arg(QString::fromStdString(fmt::to_string(other_slot)))); .arg(QString::fromStdString(path))
.arg(GCMemcardManager::GetErrorMessagesForErrorCode(error_code)));
return; return;
} }
} }
} }
QString path_old = // Check if the other slot has the same memory card configured and refuse to use this card if so.
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(slot)))) // The EU path is compared here, but it doesn't actually matter which one we compare since they
.absoluteFilePath(); // 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)
Config::SetBase(Config::GetInfoForMemcardPath(slot), path_abs.toStdString());
if (Core::IsRunning() && path_abs != path_old)
{ {
// ChangeDevice unplugs the device for 1 second, which means that games should notice that if (other_slot == slot)
// the path has changed and thus the memory card contents have changed continue;
ExpansionInterface::ChangeDevice(slot, ExpansionInterface::EXIDeviceType::MemoryCard);
const std::string other_eu_path = Config::GetMemcardPath(other_slot, DiscIO::Region::PAL);
if (eu_path == other_eu_path)
{
ModalMessageBox::critical(
this, tr("Error"),
tr("The same file can't be used in multiple slots; it is already used by %1.")
.arg(QString::fromStdString(fmt::to_string(other_slot))));
return;
}
}
Config::SetBase(Config::GetInfoForMemcardPath(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 done by a config change callback instead.
const std::string old_eu_path = Config::GetMemcardPath(slot, DiscIO::Region::PAL);
if (eu_path != old_eu_path)
{
// 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::MemoryCard);
}
} }
} }