Skip to content

Commit

Permalink
System: Share memory cards in multi-disc games
Browse files Browse the repository at this point in the history
Even without playlists.
  • Loading branch information
stenzek committed Aug 23, 2023
1 parent 7d914a9 commit c7f987b
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 32 deletions.
4 changes: 2 additions & 2 deletions src/core/fullscreen_ui.cpp
Expand Up @@ -3583,8 +3583,8 @@ void FullscreenUI::DrawMemoryCardSettingsPage()
SetSettingsChanged(bsi);
}

DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SEARCH, "Use Single Card For Sub-Images"),
FSUI_CSTR("When using a multi-disc image (m3u/pbp) and per-game (title) memory cards, "
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_FA_SEARCH, "Use Single Card For Multi-Disc Games"),
FSUI_CSTR("When playing a multi-disc game and using per-game (title) memory cards, "
"use a single memory card for all discs."),
"MemoryCards", "UsePlaylistTitle", true);

Expand Down
30 changes: 14 additions & 16 deletions src/core/pad.cpp
Expand Up @@ -192,17 +192,14 @@ bool Pad::DoStateController(StateWrapper& sw, u32 i)
if (g_settings.load_devices_from_save_states)
{
Host::AddFormattedOSDMessage(
10.0f,
TRANSLATE("OSDMessage",
"Save state contains controller type %s in port %u, but %s is used. Switching."),
10.0f, TRANSLATE("OSDMessage", "Save state contains controller type %s in port %u, but %s is used. Switching."),
Settings::GetControllerTypeName(state_controller_type), i + 1u,
Settings::GetControllerTypeName(controller_type));
}
else
{
Host::AddFormattedOSDMessage(
10.0f, TRANSLATE("OSDMessage", "Ignoring mismatched controller type %s in port %u."),
Settings::GetControllerTypeName(state_controller_type), i + 1u);
Host::AddFormattedOSDMessage(10.0f, TRANSLATE("OSDMessage", "Ignoring mismatched controller type %s in port %u."),
Settings::GetControllerTypeName(state_controller_type), i + 1u);
}

// dev-friendly untranslated console log.
Expand Down Expand Up @@ -256,8 +253,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
{
Host::AddFormattedOSDMessage(
20.0f,
TRANSLATE("OSDMessage",
"Memory card %u present in save state but not in system. Creating temporary card."),
TRANSLATE("OSDMessage", "Memory card %u present in save state but not in system. Creating temporary card."),
i + 1u);
s_memory_cards[i] = MemoryCard::Create();
}
Expand Down Expand Up @@ -296,7 +292,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
Host::AddFormattedOSDMessage(
20.0f,
TRANSLATE("OSDMessage",
"Memory card %u from save state does match current card data. Simulating replugging."),
"Memory card %u from save state does match current card data. Simulating replugging."),
i + 1u);

// this is a potentially serious issue - some games cache info from memcards and jumping around
Expand All @@ -312,8 +308,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
else
{
Host::AddFormattedOSDMessage(
20.0f,
TRANSLATE("OSDMessage", "Memory card %u present in save state but not in system. Ignoring card."),
20.0f, TRANSLATE("OSDMessage", "Memory card %u present in save state but not in system. Ignoring card."),
i + 1u);
}

Expand All @@ -325,16 +320,14 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
if (g_settings.load_devices_from_save_states)
{
Host::AddFormattedOSDMessage(
20.0f,
TRANSLATE("OSDMessage", "Memory card %u present in system but not in save state. Removing card."),
20.0f, TRANSLATE("OSDMessage", "Memory card %u present in system but not in save state. Removing card."),
i + 1u);
s_memory_cards[i].reset();
}
else
{
Host::AddFormattedOSDMessage(
20.0f,
TRANSLATE("OSDMessage", "Memory card %u present in system but not in save state. Replugging card."),
20.0f, TRANSLATE("OSDMessage", "Memory card %u present in system but not in save state. Replugging card."),
i + 1u);
s_memory_cards[i]->Reset();
}
Expand Down Expand Up @@ -545,6 +538,10 @@ MemoryCard* Pad::GetMemoryCard(u32 slot)

void Pad::SetMemoryCard(u32 slot, std::unique_ptr<MemoryCard> dev)
{
Log_InfoPrintf("Memory card slot %u: %s", slot,
dev ? (dev->GetFilename().empty() ? "<no file configured>" : dev->GetFilename().c_str()) :
"<unplugged>");

s_memory_cards[slot] = std::move(dev);
}

Expand Down Expand Up @@ -798,7 +795,8 @@ void Pad::DoTransfer(TickCount ticks_late)
const u32 frame_number = System::GetFrameNumber();

// consider u32 overflow case
if (ShouldAvoidSavingToState() && (frame_number - s_last_memory_card_transfer_frame) > GetMaximumRollbackFrames())
if (ShouldAvoidSavingToState() &&
(frame_number - s_last_memory_card_transfer_frame) > GetMaximumRollbackFrames())
BackupMemoryCardState();

s_last_memory_card_transfer_frame = frame_number;
Expand Down
2 changes: 1 addition & 1 deletion src/core/settings.cpp
Expand Up @@ -1271,7 +1271,7 @@ std::string Settings::GetSharedMemoryCardPath(u32 slot) const
return ret;
}

std::string Settings::GetGameMemoryCardPath(const char* serial, u32 slot)
std::string Settings::GetGameMemoryCardPath(const std::string_view& serial, u32 slot)
{
return Path::Combine(EmuFolders::MemoryCards, fmt::format("{}_{}.mcd", serial, slot + 1));
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/settings.h
Expand Up @@ -312,7 +312,7 @@ struct Settings
std::string GetSharedMemoryCardPath(u32 slot) const;

/// Returns the default path to a memory card for a specific game.
static std::string GetGameMemoryCardPath(const char* serial, u32 slot);
static std::string GetGameMemoryCardPath(const std::string_view& serial, u32 slot);

static void CPUOverclockPercentToFraction(u32 percent, u32* numerator, u32* denominator);
static u32 CPUOverclockFractionToPercent(u32 numerator, u32 denominator);
Expand Down
52 changes: 43 additions & 9 deletions src/core/system.cpp
Expand Up @@ -2304,8 +2304,8 @@ bool System::InternalSaveState(ByteStream* state, u32 screenshot_size /* = 256 *
u32 screenshot_stride;
GPUTexture::Format screenshot_format;
if (g_gpu_device->RenderScreenshot(screenshot_width, screenshot_height,
Common::Rectangle<s32>::FromExtents(0, 0, screenshot_width, screenshot_height),
&screenshot_buffer, &screenshot_stride, &screenshot_format) &&
Common::Rectangle<s32>::FromExtents(0, 0, screenshot_width, screenshot_height),
&screenshot_buffer, &screenshot_stride, &screenshot_format) &&
GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride,
screenshot_format))
{
Expand Down Expand Up @@ -2893,8 +2893,43 @@ std::unique_ptr<MemoryCard> System::GetMemoryCardForSlot(u32 slot, MemoryCardTyp
}
else
{
return MemoryCard::Open(g_settings.GetGameMemoryCardPath(
MemoryCard::SanitizeGameTitleForFileName(s_running_game_title).c_str(), slot));
std::string card_path;

// Playlist - use title if different.
if (HasMediaSubImages() && s_running_game_entry && s_running_game_title != s_running_game_entry->title)
{
card_path = g_settings.GetGameMemoryCardPath(
MemoryCard::SanitizeGameTitleForFileName(s_running_game_entry->title), slot);
}
// Multi-disc game - use disc set name.
else if (s_running_game_entry && !s_running_game_entry->disc_set_name.empty())
{
card_path = g_settings.GetGameMemoryCardPath(
MemoryCard::SanitizeGameTitleForFileName(s_running_game_entry->disc_set_name), slot);
}

// But prefer a disc-specific card if one already exists.
std::string disc_card_path =
g_settings.GetGameMemoryCardPath(MemoryCard::SanitizeGameTitleForFileName(s_running_game_entry->title), slot);
if (disc_card_path != card_path)
{
if (card_path.empty() || !g_settings.memory_card_use_playlist_title ||
FileSystem::FileExists(disc_card_path.c_str()))
{
if (g_settings.memory_card_use_playlist_title && !card_path.empty())
{
Host::AddIconOSDMessage(
fmt::format("DiscSpecificMC{}", slot), ICON_FA_SD_CARD,
fmt::format(TRANSLATE_FS("System", "Using disc-specific memory card '{}' instead of per-game card."),
Path::GetFileName(disc_card_path)),
Host::OSD_INFO_DURATION);
}

card_path = std::move(disc_card_path);
}
}

return MemoryCard::Open(card_path.c_str());
}
}

Expand Down Expand Up @@ -3145,9 +3180,9 @@ void System::UpdateRunningGame(const char* path, CDImage* image, bool booting)
s_running_game_title = Path::GetFileTitle(FileSystem::GetDisplayNameFromPath(path));
}

if (image->HasSubImages() && g_settings.memory_card_use_playlist_title)
if (image->HasSubImages())
{
std::string image_title(image->GetMetadata("title"));
std::string image_title = image->GetMetadata("title");
if (!image_title.empty())
s_running_game_title = std::move(image_title);
}
Expand Down Expand Up @@ -3456,8 +3491,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)

if (g_settings.memory_card_types != old_settings.memory_card_types ||
g_settings.memory_card_paths != old_settings.memory_card_paths ||
(g_settings.memory_card_use_playlist_title != old_settings.memory_card_use_playlist_title &&
HasMediaSubImages()))
(g_settings.memory_card_use_playlist_title != old_settings.memory_card_use_playlist_title))
{
UpdateMemoryCardTypes();
}
Expand Down Expand Up @@ -4448,7 +4482,7 @@ void System::UpdateSoftwareCursor()
if (image && image->IsValid())
{
g_gpu_device->SetSoftwareCursor(image->GetPixels(), image->GetWidth(), image->GetHeight(), image->GetPitch(),
image_scale);
image_scale);
}
else
{
Expand Down
6 changes: 3 additions & 3 deletions src/duckstation-qt/memorycardsettingswidget.cpp
Expand Up @@ -58,13 +58,13 @@ void MemoryCardSettingsWidget::createUi(SettingsDialog* dialog)
box_layout->addLayout(hbox);
}

QCheckBox* playlist_title_as_game_title = new QCheckBox(tr("Use Single Card For Sub-Images"), box);
QCheckBox* playlist_title_as_game_title = new QCheckBox(tr("Use Single Card For Multi-Disc Games"), box);
SettingWidgetBinder::BindWidgetToBoolSetting(m_dialog->getSettingsInterface(), playlist_title_as_game_title,
"MemoryCards", "UsePlaylistTitle", true);
box_layout->addWidget(playlist_title_as_game_title);
dialog->registerWidgetHelp(
playlist_title_as_game_title, tr("Use Single Card For Sub-Images"), tr("Checked"),
tr("When using a multi-disc format (m3u/pbp) and per-game (title) memory cards, a single memory card "
playlist_title_as_game_title, tr("Use Single Card For Multi-Disc Games"), tr("Checked"),
tr("When playing a multi-disc game and using per-game (title) memory cards, a single memory card "
"will be used for all discs. If unchecked, a separate card will be used for each disc."));

box_layout->addWidget(QtUtils::CreateHorizontalLine(box));
Expand Down

0 comments on commit c7f987b

Please sign in to comment.