From f214fcf10332b1b96c4436b54fae2d96dc9c3873 Mon Sep 17 00:00:00 2001 From: Haisom Date: Sat, 10 May 2025 11:52:01 -0300 Subject: [PATCH] Core/Qt: Add hotkey support for swapping memory card ### Description of Changes - Adds Swap Memory Card function. - Adds Helper Function to verify whenever memory cards are auto-ejecting. - Adds assert to make sure the memory cards swapping function will only RunOnCPUThreat. - Adds field to set a custom hotkey under Controllers>Hotkeys to quickly swap memory cards. ### Rationale behind Changes - Allow users to change memory cards on demand. This is really useful on shared machines, specially with kids around (Forget kids accidentally overwriting your save games with over 100 hours of gameplay!). - This will easy up process for saving on backup memory cards on the fly without the need of auxiliary tools such as "mymc". - By creating a memory card swap function in the core, we can now use it anywhere. ### Suggested Testing Steps - Assign hotkey under Controllers>Hotkeys>Swap Memory Cards and test while on BIOS Browser or in game. Special thanks to @kamfretoz @RedDevilus , @RedPanda4552 , and @Mrlinkwii for the feedback, suggestions and troubleshooting! Co-Authored-By: pandubz Co-Authored-By: KamFretoZ <14798312+kamfretoz@users.noreply.github.com> --- pcsx2/Hotkeys.cpp | 6 +++ pcsx2/SIO/Memcard/MemoryCardFile.cpp | 64 ++++++++++++++++++++++++++++ pcsx2/SIO/Memcard/MemoryCardFile.h | 1 + 3 files changed, 71 insertions(+) diff --git a/pcsx2/Hotkeys.cpp b/pcsx2/Hotkeys.cpp index 8a74ac57f5..38fdc870cd 100644 --- a/pcsx2/Hotkeys.cpp +++ b/pcsx2/Hotkeys.cpp @@ -11,6 +11,7 @@ #include "Recording/InputRecording.h" #include "SPU2/spu2.h" #include "VMManager.h" +#include "SIO/Memcard/MemoryCardFile.h" #include "common/Assertions.h" #include "common/FileSystem.h" @@ -223,6 +224,11 @@ DEFINE_HOTKEY("InputRecToggleMode", TRANSLATE_NOOP("Hotkeys", "System"), if (!pressed && VMManager::HasValidVM()) g_InputRecording.getControls().toggleRecordMode(); }) +DEFINE_HOTKEY("SwapMemCards", TRANSLATE_NOOP("Hotkeys", "System"), + TRANSLATE_NOOP("Hotkeys", "Swap Memory Cards"), [](s32 pressed) { + if (!pressed && VMManager::HasValidVM()) + FileMcd_Swap(); + }) DEFINE_HOTKEY("PreviousSaveStateSlot", TRANSLATE_NOOP("Hotkeys", "Save States"), TRANSLATE_NOOP("Hotkeys", "Select Previous Save Slot"), [](s32 pressed) { diff --git a/pcsx2/SIO/Memcard/MemoryCardFile.cpp b/pcsx2/SIO/Memcard/MemoryCardFile.cpp index fc76c1416d..e340f1f34d 100644 --- a/pcsx2/SIO/Memcard/MemoryCardFile.cpp +++ b/pcsx2/SIO/Memcard/MemoryCardFile.cpp @@ -5,6 +5,7 @@ #include "SIO/Memcard/MemoryCardFolder.h" #include "SIO/Sio.h" +#include #include "common/Assertions.h" #include "common/Console.h" @@ -18,6 +19,7 @@ #include "Config.h" #include "Host.h" +#include "VMManager.h" #include "IconsPromptFont.h" #include "fmt/format.h" @@ -636,6 +638,68 @@ void FileMcd_Reopen(std::string new_serial) FileMcd_EmuOpen(); } +static bool FileMcd_IsAutoEjecting() +{ + for (size_t port = 0; port < SIO::PORTS; ++port) + { + for (size_t slot = 0; slot < SIO::SLOTS; ++slot) + { + if (mcds[port][slot].autoEjectTicks > 0) + { + return true; // Auto-eject is active + } + } + } + return false; // No auto-eject active +} + +void FileMcd_Swap() +{ + //Assert that this is called on the CPU thread + pxAssert(Host::RunOnCPUThread([]() { return true; }, true) && "FileMcd_Swap must be called under Host::RunOnCPUThread"); + + if (MemcardBusy::IsBusy()) + { + Host::AddIconOSDMessage("MemoryCardSwap_Busy", ICON_PF_MEMORY_CARD, TRANSLATE_SV("MemoryCardSwap_Busy", "Memory cards are busy. Can't swap right now.")); + return; + } + + // Check if auto-eject is active + if (FileMcd_IsAutoEjecting()) + { + Host::AddIconOSDMessage("MemoryCardSwap_AutoEject", ICON_PF_MEMORY_CARD, TRANSLATE_SV("MemoryCardSwap_AutoEject", "Memory cards are being auto-ejected. Can't swap right now.")); + return; + } + + const std::string card1Filename = Host::GetStringSettingValue("MemoryCards", "Slot1_Filename"); + const std::string card2Filename = Host::GetStringSettingValue("MemoryCards", "Slot2_Filename"); + + // Copy each McdOptions to local memory + Pcsx2Config::McdOptions firstSlot = EmuConfig.Mcd[0]; + Pcsx2Config::McdOptions secondSlot = EmuConfig.Mcd[1]; + + if (!firstSlot.Enabled || !secondSlot.Enabled || card1Filename.empty() || card2Filename.empty()) + { + Host::AddIconOSDMessage("MemoryCardSwap_EmptySlot", ICON_PF_MEMORY_CARD, TRANSLATE_SV("MemoryCard_EmptySlot", "Both slots must have a card selected to swap.")); + return; + } + + // Swap them + Host::SetBaseStringSettingValue("MemoryCards", "Slot1_Filename", card2Filename.c_str()); + Host::SetBaseStringSettingValue("MemoryCards", "Slot2_Filename", card1Filename.c_str()); + Host::CommitBaseSettingChanges(); + VMManager::ApplySettings(); + EmuConfig.Mcd[0] = secondSlot; + EmuConfig.Mcd[1] = firstSlot; + + // Reopen them + FileMcd_EmuClose(); + FileMcd_SetType(); + FileMcd_EmuOpen(); + AutoEject::SetAll(); + Host::AddIconOSDMessage("MemoryCardSwap", ICON_PF_MEMORY_CARD, fmt::format(TRANSLATE_FS("MemoryCardSwap", "Memory Cards have been swapped.\nSlot 1: {}\nSlot 2: {}"), EmuConfig.Mcd[0].Filename, EmuConfig.Mcd[1].Filename), Host::OSD_INFO_DURATION); +} + s32 FileMcd_IsPresent(uint port, uint slot) { const uint combinedSlot = FileMcd_ConvertToSlot(port, slot); diff --git a/pcsx2/SIO/Memcard/MemoryCardFile.h b/pcsx2/SIO/Memcard/MemoryCardFile.h index 08419b9007..44d40619c7 100644 --- a/pcsx2/SIO/Memcard/MemoryCardFile.h +++ b/pcsx2/SIO/Memcard/MemoryCardFile.h @@ -42,6 +42,7 @@ void FileMcd_EmuOpen(); void FileMcd_EmuClose(); void FileMcd_CancelEject(); void FileMcd_Reopen(std::string new_serial); +void FileMcd_Swap(); s32 FileMcd_IsPresent(uint port, uint slot); void FileMcd_GetSizeInfo(uint port, uint slot, McdSizeInfo* outways); bool FileMcd_IsPSX(uint port, uint slot);