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 <redpanda4552@gmail.com>
Co-Authored-By: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
This commit is contained in:
Haisom 2025-05-10 11:52:01 -03:00
parent f34db72a97
commit f214fcf103
3 changed files with 71 additions and 0 deletions

View File

@ -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) {

View File

@ -5,6 +5,7 @@
#include "SIO/Memcard/MemoryCardFolder.h"
#include "SIO/Sio.h"
#include <SIO/SioTypes.h>
#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);

View File

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