@@ -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()
{
if (!Config::Get(Config::SESSION_SAVE_DATA_WRITABLE))
@@ -20,8 +20,6 @@ class MemoryCard : public MemoryCardBase
MemoryCard(const std::string& filename, ExpansionInterface::Slot card_slot,
u16 size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_2043);
~MemoryCard();
static void CheckPath(std::string& memcardPath, const std::string& gameRegion,
ExpansionInterface::Slot slot);
void FlushThread();
void MakeDirty();

@@ -1479,18 +1479,20 @@ void GetSettings()
}
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 [path, migrate] = ExpansionInterface::CEXIMemoryCard::GetGCIFolderPath(
card_slot, ExpansionInterface::AllowMovieFolder::No);
const u64 number_of_saves = File::ScanDirectoryTree(path, false).size;
return number_of_saves > 0;
};

s_bClearSave =
!(slot_a_has_raw_memcard && File::Exists(Config::Get(Config::MAIN_MEMCARD_A_PATH))) &&
!(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_b_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::B));
s_bClearSave = !(slot_a_has_raw_memcard && raw_memcard_exists(ExpansionInterface::Slot::A)) &&
!(slot_b_has_raw_memcard && raw_memcard_exists(ExpansionInterface::Slot::B)) &&
!(slot_a_has_gci_folder && gci_folder_has_saves(ExpansionInterface::Slot::A)) &&
!(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_b_has_raw_memcard || slot_b_has_gci_folder) << 1;
@@ -1470,8 +1470,8 @@ bool NetPlayServer::StartGame()

const sf::Uint64 initial_rtc = GetInitialNetPlayRTC();

const std::string region = SConfig::GetDirectoryForRegion(
SConfig::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game_identifier)->GetRegion()));
const std::string region = Config::GetDirectoryForRegion(
Config::ToGameCubeRegion(m_dialog->FindGameFile(m_selected_game_identifier)->GetRegion()));

// sync GC SRAM with clients
if (!g_SRAM_netplay_initialized)
@@ -1665,26 +1665,21 @@ bool NetPlayServer::SyncSaveData()
if (save_count == 0)
return true;

const std::string region =
SConfig::GetDirectoryForRegion(SConfig::ToGameCubeRegion(game->GetRegion()));
const auto game_region = game->GetRegion();
const std::string region = Config::GetDirectoryForRegion(Config::ToGameCubeRegion(game_region));

for (ExpansionInterface::Slot slot : ExpansionInterface::MEMCARD_SLOTS)
{
const bool is_slot_a = slot == ExpansionInterface::Slot::A;

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;
if (size_override >= 0 && size_override <= 4)
{
path.insert(path.find_last_of('.'),
fmt::format(".{}", Memcard::MbitToFreeBlocks(Memcard::MBIT_SIZE_MEMORY_CARD_59
<< size_override)));
}
const u16 card_size_mbits =
size_override >= 0 && size_override <= 4 ?
static_cast<u16>(Memcard::MBIT_SIZE_MEMORY_CARD_59 << size_override) :
Memcard::MBIT_SIZE_MEMORY_CARD_2043;
const std::string path = Config::GetMemcardPath(slot, game_region, card_size_mbits);

sf::Packet pac;
pac << MessageID::SyncSaveData;
@@ -228,7 +228,8 @@ void GCMemcardManager::LoadDefaultMemcards()
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);
}
}
@@ -713,7 +713,7 @@ void GameList::OpenGCSaveFolder()
case ExpansionInterface::EXIDeviceType::MemoryCardFolder:
{
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");

std::string override_path = Config::Get(Config::GetInfoForGCIPathOverride(slot));
@@ -734,7 +734,7 @@ void GameList::OpenGCSaveFolder()
}
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;

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

@@ -303,66 +303,89 @@ void GameCubePane::BrowseMemcard(ExpansionInterface::Slot slot)
{
ASSERT(ExpansionInterface::IsMemcardSlot(slot));

QString filename = DolphinFileDialog::getSaveFileName(
this, tr("Choose a file to open"), QString::fromStdString(File::GetUserPath(D_GCUSER_IDX)),
const QString filename = DolphinFileDialog::getSaveFileName(
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);

if (filename.isEmpty())
return;

QString path_abs = QFileInfo(filename).absoluteFilePath();
const std::string raw_path =
WithUnifiedPathSeparators(QFileInfo(filename).absoluteFilePath().toStdString());

// Memcard validity checks
if (File::Exists(filename.toStdString()))
{
auto [error_code, mc] = Memcard::GCMemcard::Open(filename.toStdString());
// Figure out if the user selected a card that has a valid region specifier in the filename.
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 (error_code.HasCriticalErrors() || !mc || !mc->IsValid())
{
ModalMessageBox::critical(
this, tr("Error"),
tr("The file\n%1\nis either corrupted or not a GameCube memory card file.\n%2")
.arg(filename)
.arg(GCMemcardManager::GetErrorMessagesForErrorCode(error_code)));
return;
}
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 filename %1 does not conform to Dolphin's region code format "
"for memory cards. Please rename this file to either %2, %3, or "
"%4, matching the region of the save files that are on 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;
}

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)
continue;

bool other_slot_memcard = m_slot_combos[other_slot]->currentData().toInt() ==
static_cast<int>(ExpansionInterface::EXIDeviceType::MemoryCard);
if (other_slot_memcard)
if (File::Exists(path))
{
QString path_other =
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(other_slot))))
.absoluteFilePath();
auto [error_code, mc] = Memcard::GCMemcard::Open(path);

if (path_abs == path_other)
if (error_code.HasCriticalErrors() || !mc || !mc->IsValid())
{
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))));
tr("The file\n%1\nis either corrupted or not a GameCube memory card file.\n%2")
.arg(QString::fromStdString(path))
.arg(GCMemcardManager::GetErrorMessagesForErrorCode(error_code)));
return;
}
}
}

QString path_old =
QFileInfo(QString::fromStdString(Config::Get(Config::GetInfoForMemcardPath(slot))))
.absoluteFilePath();
// Check if the other slot has the same memory card configured and refuse to use this card 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::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), path_abs.toStdString());
Config::SetBase(Config::GetInfoForMemcardPath(slot), raw_path);

if (Core::IsRunning() && path_abs != path_old)
if (Core::IsRunning())
{
// 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);
// 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);
}
}
}