From 74e455a5f7ef839619ebc4f1aa07048681b5d180 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Mon, 27 Apr 2020 16:15:38 +1000 Subject: [PATCH] System: Support per-game memory cards --- src/core/host_interface.cpp | 12 ++++++-- src/core/memory_card.cpp | 4 +++ src/core/settings.cpp | 53 ++++++++++++++++++++++++++++------- src/core/settings.h | 10 +++++++ src/core/system.cpp | 56 +++++++++++++++++++++++++++++++++---- src/core/types.h | 8 ++++++ 6 files changed, 125 insertions(+), 18 deletions(-) diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 4a97f671b..4379b2ea9 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -769,7 +769,7 @@ std::string HostInterface::GetSharedMemoryCardPath(u32 slot) const std::string HostInterface::GetGameMemoryCardPath(const char* game_code, u32 slot) const { - return GetUserDirectoryRelativePath("memcards/game_card_%s_%d.mcd", game_code, slot + 1); + return GetUserDirectoryRelativePath("memcards/%s_%d.mcd", game_code, slot + 1); } std::vector HostInterface::GetAvailableSaveStates(const char* game_code) const @@ -959,8 +959,10 @@ void HostInterface::SetDefaultSettings(SettingsInterface& si) si.SetStringValue("Controller1", "Type", Settings::GetControllerTypeName(ControllerType::DigitalController)); si.SetStringValue("Controller2", "Type", Settings::GetControllerTypeName(ControllerType::None)); + si.SetStringValue("MemoryCards", "Card1Type", "PerGame"); si.SetStringValue("MemoryCards", "Card1Path", "memcards/shared_card_1.mcd"); - si.SetStringValue("MemoryCards", "Card2Path", ""); + si.SetStringValue("MemoryCards", "Card2Type", "None"); + si.SetStringValue("MemoryCards", "Card2Path", "memcards/shared_card_2.mcd"); si.SetBoolValue("Debug", "ShowVRAM", false); si.SetBoolValue("Debug", "DumpCPUToVRAMCopies", false); @@ -996,6 +998,9 @@ void HostInterface::UpdateSettings(const std::function& apply_callback) const bool old_display_linear_filtering = m_settings.display_linear_filtering; const bool old_cdrom_read_thread = m_settings.cdrom_read_thread; std::array old_controller_types = m_settings.controller_types; + std::array old_memory_card_types = m_settings.memory_card_types; + std::array old_memory_card_paths = + std::move(m_settings.memory_card_paths); apply_callback(); @@ -1050,6 +1055,9 @@ void HostInterface::UpdateSettings(const std::function& apply_callback) if (m_settings.cdrom_read_thread != old_cdrom_read_thread) m_system->GetCDROM()->SetUseReadThread(m_settings.cdrom_read_thread); + + if (m_settings.memory_card_types != old_memory_card_types || m_settings.memory_card_paths != old_memory_card_paths) + m_system->UpdateMemoryCards(); } bool controllers_updated = false; diff --git a/src/core/memory_card.cpp b/src/core/memory_card.cpp index 0f2c8e992..f48b62f5b 100644 --- a/src/core/memory_card.cpp +++ b/src/core/memory_card.cpp @@ -254,6 +254,7 @@ std::unique_ptr MemoryCard::Open(System* system, std::string_view fi message.AppendString(filename.data(), static_cast(filename.length())); message.AppendString("' could not be read, formatting."); Log_ErrorPrint(message); + system->GetHostInterface()->AddOSDMessage(message, 5.0f); mc->Format(); } @@ -348,6 +349,7 @@ bool MemoryCard::LoadFromFile() return false; } + Log_InfoPrintf("Loaded memory card from %s", m_filename.c_str()); return true; } @@ -381,8 +383,10 @@ bool MemoryCard::SaveIfChanged(bool display_osd_message) Log_InfoPrintf("Saved memory card to '%s'", m_filename.c_str()); if (display_osd_message) + { m_system->GetHostInterface()->AddOSDMessage( SmallString::FromFormat("Saved memory card to '%s'", m_filename.c_str())); + } return true; } diff --git a/src/core/settings.cpp b/src/core/settings.cpp index d6e8c0552..30a8c42cd 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -63,8 +63,18 @@ void Settings::Load(SettingsInterface& si) controller_types[1] = ParseControllerTypeName(si.GetStringValue("Controller2", "Type", "None").c_str()).value_or(ControllerType::None); + // NOTE: The default value here if not present in the config is shared, but SetDefaultSettings() makes per-game. + // This is so we don't break older builds which had the shared card by default. + memory_card_types[0] = + ParseMemoryCardTypeName( + si.GetStringValue("MemoryCards", "Card1Type", GetMemoryCardTypeName(MemoryCardType::Shared)).c_str()) + .value_or(MemoryCardType::Shared); memory_card_paths[0] = si.GetStringValue("MemoryCards", "Card1Path", "memcards/shared_card_1.mcd"); - memory_card_paths[1] = si.GetStringValue("MemoryCards", "Card2Path", ""); + memory_card_types[1] = + ParseMemoryCardTypeName( + si.GetStringValue("MemoryCards", "Card2Type", GetMemoryCardTypeName(MemoryCardType::None)).c_str()) + .value_or(MemoryCardType::None); + memory_card_paths[1] = si.GetStringValue("MemoryCards", "Card2Path", "memcards/shared_card_2.mcd"); debugging.show_vram = si.GetBoolValue("Debug", "ShowVRAM"); debugging.dump_cpu_to_vram_copies = si.GetBoolValue("Debug", "DumpCPUToVRAMCopies"); @@ -129,15 +139,10 @@ void Settings::Save(SettingsInterface& si) const else si.DeleteValue("Controller2", "Type"); - if (!memory_card_paths[0].empty()) - si.SetStringValue("MemoryCards", "Card1Path", memory_card_paths[0].c_str()); - else - si.DeleteValue("MemoryCards", "Card1Path"); - - if (!memory_card_paths[1].empty()) - si.SetStringValue("MemoryCards", "Card2Path", memory_card_paths[1].c_str()); - else - si.DeleteValue("MemoryCards", "Card2Path"); + si.SetStringValue("MemoryCards", "Card1Type", GetMemoryCardTypeName(memory_card_types[0])); + si.SetStringValue("MemoryCards", "Card1Path", memory_card_paths[0].c_str()); + si.SetStringValue("MemoryCards", "Card2Type", GetMemoryCardTypeName(memory_card_types[1])); + si.SetStringValue("MemoryCards", "Card2Path", memory_card_paths[1].c_str()); si.SetBoolValue("Debug", "ShowVRAM", debugging.show_vram); si.SetBoolValue("Debug", "DumpCPUToVRAMCopies", debugging.dump_cpu_to_vram_copies); @@ -377,3 +382,31 @@ const char* Settings::GetControllerTypeDisplayName(ControllerType type) { return s_controller_display_names[static_cast(type)]; } + +static std::array s_memory_card_type_names = {{"None", "Shared", "PerGame"}}; +static std::array s_memory_card_type_display_names = { + {"No Memory Card", "Shared Between All Games", "Separate Card Per Game"}}; + +std::optional Settings::ParseMemoryCardTypeName(const char* str) +{ + int index = 0; + for (const char* name : s_memory_card_type_names) + { + if (StringUtil::Strcasecmp(name, str) == 0) + return static_cast(index); + + index++; + } + + return std::nullopt; +} + +const char* Settings::GetMemoryCardTypeName(MemoryCardType type) +{ + return s_memory_card_type_names[static_cast(type)]; +} + +const char* Settings::GetMemoryCardTypeDisplayName(MemoryCardType type) +{ + return s_memory_card_type_display_names[static_cast(type)]; +} diff --git a/src/core/settings.h b/src/core/settings.h index 86e48a29b..a79032f45 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -92,8 +92,14 @@ struct Settings bool bios_patch_fast_boot = false; std::array controller_types{}; + std::array memory_card_types{}; std::array memory_card_paths{}; + ALWAYS_INLINE bool HasAnyPerGameMemoryCards() const + { + return (memory_card_types[0] == MemoryCardType::PerGame || memory_card_types[1] == MemoryCardType::PerGame); + } + void Load(SettingsInterface& si); void Save(SettingsInterface& si) const; @@ -129,6 +135,10 @@ struct Settings static const char* GetControllerTypeName(ControllerType type); static const char* GetControllerTypeDisplayName(ControllerType type); + static std::optional ParseMemoryCardTypeName(const char* str); + static const char* GetMemoryCardTypeName(MemoryCardType type); + static const char* GetMemoryCardTypeDisplayName(MemoryCardType type); + // Default to D3D11 on Windows as it's more performant and at this point, less buggy. #ifdef WIN32 static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::HardwareD3D11; diff --git a/src/core/system.cpp b/src/core/system.cpp index 1d103d720..13faf430f 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -191,6 +191,9 @@ bool System::Boot(const SystemBootParameters& params) return false; } + // Notify change of disc. + UpdateRunningGame(params.filename.c_str(), media.get()); + // Component setup. InitializeComponents(); UpdateControllers(); @@ -214,9 +217,6 @@ bool System::Boot(const SystemBootParameters& params) return false; } - // Notify change of disc. - UpdateRunningGame(params.filename.c_str(), media.get()); - // Insert CD, and apply fastboot patch if enabled. if (media) m_cdrom->InsertMedia(std::move(media)); @@ -798,10 +798,47 @@ void System::UpdateMemoryCards() for (u32 i = 0; i < NUM_CONTROLLER_AND_CARD_PORTS; i++) { m_pad->SetMemoryCard(i, nullptr); - if (settings.memory_card_paths[i].empty()) - continue; - std::unique_ptr card = MemoryCard::Open(this, settings.memory_card_paths[i]); + const MemoryCardType type = settings.memory_card_types[i]; + std::unique_ptr card; + switch (settings.memory_card_types[i]) + { + case MemoryCardType::None: + continue; + + case MemoryCardType::PerGame: + { + if (m_running_game_code.empty()) + { + m_host_interface->AddFormattedOSDMessage(5.0f, + "Per-game memory card cannot be used for slot %u as the running " + "game has no code. Using shared card instead.", + i + 1u); + card = MemoryCard::Open(this, m_host_interface->GetSharedMemoryCardPath(i)); + } + else + { + card = MemoryCard::Open(this, m_host_interface->GetGameMemoryCardPath(m_running_game_code.c_str(), i)); + } + } + break; + + case MemoryCardType::Shared: + { + if (settings.memory_card_paths[i].empty()) + { + m_host_interface->AddFormattedOSDMessage(2.0f, "Memory card path for slot %u is missing, using default.", + i + 1u); + card = MemoryCard::Open(this, m_host_interface->GetSharedMemoryCardPath(i)); + } + else + { + card = MemoryCard::Open(this, settings.memory_card_paths[i]); + } + } + break; + } + if (card) m_pad->SetMemoryCard(i, std::move(card)); } @@ -820,6 +857,13 @@ bool System::InsertMedia(const char* path) UpdateRunningGame(path, image.get()); m_cdrom->InsertMedia(std::move(image)); + + if (GetSettings().HasAnyPerGameMemoryCards()) + { + m_host_interface->AddOSDMessage("Game changed, reloading memory cards.", 2.0f); + UpdateMemoryCards(); + } + return true; } diff --git a/src/core/types.h b/src/core/types.h index a918a1ce6..b4e3a0753 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -91,6 +91,14 @@ enum class ControllerType Count }; +enum class MemoryCardType +{ + None, + Shared, + PerGame, + Count +}; + enum : u32 { NUM_CONTROLLER_AND_CARD_PORTS = 2