diff --git a/Source/Core/Common/StringUtil.cpp b/Source/Core/Common/StringUtil.cpp index a752ab0e10..c498e261fd 100644 --- a/Source/Core/Common/StringUtil.cpp +++ b/Source/Core/Common/StringUtil.cpp @@ -335,6 +335,23 @@ bool SplitPath(std::string_view full_path, std::string* path, std::string* filen 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 file_name, extension; diff --git a/Source/Core/Common/StringUtil.h b/Source/Core/Common/StringUtil.h index 56fa756a46..5db7cbfa37 100644 --- a/Source/Core/Common/StringUtil.h +++ b/Source/Core/Common/StringUtil.h @@ -157,9 +157,17 @@ std::vector SplitString(const std::string& str, char delim); std::string JoinStrings(const std::vector& strings, const std::string& delimiter); // "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, 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); bool StringBeginsWith(std::string_view str, std::string_view begin); diff --git a/Source/Core/Core/Boot/Boot.cpp b/Source/Core/Core/Boot/Boot.cpp index 0ad7f6ec12..4e2844c95b 100644 --- a/Source/Core/Core/Boot/Boot.cpp +++ b/Source/Core/Core/Boot/Boot.cpp @@ -311,8 +311,8 @@ std::unique_ptr BootParameters::GenerateFromFile(std::vector>& 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 diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 4ed43a79da..e1b538f2b6 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -331,4 +331,15 @@ extern const Info MAIN_USB_PASSTHROUGH_DEVICES; std::set> GetUSBDeviceWhitelist(); void SetUSBDeviceWhitelist(const std::set>& 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 diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp index 2d90207601..497d938f76 100644 --- a/Source/Core/Core/ConfigManager.cpp +++ b/Source/Core/Core/ConfigManager.cpp @@ -220,53 +220,6 @@ std::string SConfig::MakeGameID(std::string_view file_name) 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 { SetGameMetadata(SConfig* config_, DiscIO::Region* region_) : config(config_), region(region_) {} @@ -375,21 +328,16 @@ bool SConfig::SetPathsAndGameMetadata(const BootParameters& boot) return false; if (m_region == DiscIO::Region::Unknown) - m_region = GetFallbackRegion(); + m_region = Config::Get(Config::MAIN_FALLBACK_REGION); // 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_strBootROM = GetBootROMPath(region_dir); + m_strBootROM = Config::GetBootROMPath(region_dir); return true; } -DiscIO::Region SConfig::GetFallbackRegion() -{ - return Config::Get(Config::MAIN_FALLBACK_REGION); -} - DiscIO::Language SConfig::GetCurrentLanguage(bool wii) const { DiscIO::Language language; diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h index bb0b165249..6a6045c0f8 100644 --- a/Source/Core/Core/ConfigManager.h +++ b/Source/Core/Core/ConfigManager.h @@ -72,13 +72,7 @@ struct SConfig void LoadDefaults(); 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); - static DiscIO::Region GetFallbackRegion(); DiscIO::Language GetCurrentLanguage(bool wii) const; DiscIO::Language GetLanguageAdjustedForRegion(bool wii, DiscIO::Region region) const; diff --git a/Source/Core/Core/HW/EXI/EXI.cpp b/Source/Core/Core/HW/EXI/EXI.cpp index eb1d99e81e..c480e09235 100644 --- a/Source/Core/Core/HW/EXI/EXI.cpp +++ b/Source/Core/Core/HW/EXI/EXI.cpp @@ -117,7 +117,7 @@ void Init() if (size_override >= 0 && size_override <= 4) size_mbits = Memcard::MBIT_SIZE_MEMORY_CARD_59 << size_override; 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 u32 rtc_bias = g_SRAM.settings.rtc_bias; const u32 sram_language = static_cast(g_SRAM.settings.language); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp index c7538ceae2..83263ed04d 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp @@ -162,8 +162,8 @@ CEXIMemoryCard::GetGCIFolderPath(Slot card_slot, AllowMovieFolder allow_movie_fo if (use_movie_folder) path += "Movie" DIR_SEP; - const DiscIO::Region region = SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region); - path = path + SConfig::GetDirectoryForRegion(region) + DIR_SEP + + 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}; } @@ -188,7 +188,7 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data) if (!file_info.Exists()) { 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 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); if (migrate) - MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot); + MigrateFromMemcardFile(dir_path + DIR_SEP, m_card_slot, SConfig::GetInstance().m_region); else File::CreateFullPath(dir_path + DIR_SEP); } @@ -218,22 +218,16 @@ void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data) 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) && Movie::IsStartingFromClearSave()) { filename = File::GetUserPath(D_GCUSER_IDX) + fmt::format("Movie{}.raw", s_card_short_names[m_card_slot]); } - - 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) + else { - filename.insert(filename.find_last_of('.'), - fmt::format(".{}", Memcard::MbitToFreeBlocks(size_mb))); + filename = Config::GetMemcardPath(m_card_slot, SConfig::GetInstance().m_region, size_mb); } m_memory_card = std::make_unique(filename, m_card_slot, size_mb); diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index b65d7f0d03..f5a46046c0 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -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); - 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)) { auto [error_code, memcard] = Memcard::GCMemcard::Open(ini_memcard.c_str()); diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h index fa9ae44110..5b02700a61 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h @@ -12,10 +12,12 @@ #include "Core/HW/GCMemcard/GCIFile.h" #include "Core/HW/GCMemcard/GCMemcard.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 //#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 { diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp index 923f88bc69..23bca41df1 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp @@ -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)) diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h index b8486c13a3..c6c85313e8 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.h @@ -20,8 +20,6 @@ public: 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(); diff --git a/Source/Core/Core/Movie.cpp b/Source/Core/Core/Movie.cpp index 938a17f2eb..f988fc7e65 100644 --- a/Source/Core/Core/Movie.cpp +++ b/Source/Core/Core/Movie.cpp @@ -1479,6 +1479,9 @@ 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); @@ -1486,11 +1489,10 @@ void GetSettings() 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; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 30a0311910..0ed2290b0d 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -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,8 +1665,8 @@ 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) { @@ -1674,17 +1674,12 @@ bool NetPlayServer::SyncSaveData() 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(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; diff --git a/Source/Core/DolphinQt/GCMemcardManager.cpp b/Source/Core/DolphinQt/GCMemcardManager.cpp index 83a6b231c3..ebfb919777 100644 --- a/Source/Core/DolphinQt/GCMemcardManager.cpp +++ b/Source/Core/DolphinQt/GCMemcardManager.cpp @@ -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); } } diff --git a/Source/Core/DolphinQt/GameList/GameList.cpp b/Source/Core/DolphinQt/GameList/GameList.cpp index 03bc60e7b2..b2fdb46df2 100644 --- a/Source/Core/DolphinQt/GameList/GameList.cpp +++ b/Source/Core/DolphinQt/GameList/GameList.cpp @@ -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; diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 1ace1276b8..a5b0adca99 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -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); diff --git a/Source/Core/DolphinQt/Settings/GameCubePane.cpp b/Source/Core/DolphinQt/Settings/GameCubePane.cpp index 208535b8ae..82aea64a23 100644 --- a/Source/Core/DolphinQt/Settings/GameCubePane.cpp +++ b/Source/Core/DolphinQt/Settings/GameCubePane.cpp @@ -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())) + // 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 (!raw_path_valid) { - auto [error_code, mc] = Memcard::GCMemcard::Open(filename.toStdString()); - - 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; - } + // 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(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(); - - Config::SetBase(Config::GetInfoForMemcardPath(slot), path_abs.toStdString()); - - if (Core::IsRunning() && path_abs != path_old) + // 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) { - // 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 (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), 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); + } } }