Skip to content

Commit

Permalink
Pad: Fix unconditional memory card replug on load state
Browse files Browse the repository at this point in the history
  • Loading branch information
stenzek committed Mar 29, 2024
1 parent e54ad19 commit c41563c
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 27 deletions.
15 changes: 14 additions & 1 deletion src/core/memory_card.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <[email protected]>
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)

#include "memory_card.h"
Expand Down Expand Up @@ -65,6 +65,19 @@ bool MemoryCard::DoState(StateWrapper& sw)
return !sw.HasError();
}

void MemoryCard::CopyState(const MemoryCard* src)
{
DebugAssert(m_data == src->m_data);

m_state = src->m_state;
m_FLAG.bits = src->m_FLAG.bits;
m_address = src->m_address;
m_sector_offset = src->m_sector_offset;
m_checksum = src->m_checksum;
m_last_byte = src->m_last_byte;
m_changed = src->m_changed;
}

void MemoryCard::ResetTransferState()
{
m_state = State::Idle;
Expand Down
1 change: 1 addition & 0 deletions src/core/memory_card.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class MemoryCard final

void Reset();
bool DoState(StateWrapper& sw);
void CopyState(const MemoryCard* src);

void ResetTransferState();
bool Transfer(const u8 data_in, u8* data_out);
Expand Down
63 changes: 37 additions & 26 deletions src/core/pad.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <[email protected]> and contributors.
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <[email protected]> and contributors.
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)

#include "pad.h"
Expand All @@ -19,6 +19,8 @@
#include "common/fifo_queue.h"
#include "common/log.h"

#include "IconsFontAwesome5.h"

#include <array>
#include <memory>

Expand Down Expand Up @@ -257,16 +259,16 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)

if (card_present_in_state && !s_memory_cards[i] && g_settings.load_devices_from_save_states)
{
Host::AddFormattedOSDMessage(
20.0f,
TRANSLATE("OSDMessage", "Memory card %u present in save state but not in system. Creating temporary card."),
i + 1u);
Host::AddIconOSDMessage(
fmt::format("card_load_warning_{}", i), ICON_FA_SD_CARD,
fmt::format(
TRANSLATE_FS("OSDMessage", "Memory card {} present in save state but not in system. Creating temporary card."),
i + 1u),
Host::OSD_ERROR_DURATION);
s_memory_cards[i] = MemoryCard::Create();
}

MemoryCard* card_ptr = s_memory_cards[i].get();
std::unique_ptr<MemoryCard> card_from_state;

if (card_present_in_state)
{
if (sw.IsReading() && !g_settings.load_devices_from_save_states)
Expand All @@ -284,38 +286,42 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
if (sw.IsWriting())
return true; // all done as far as writes concerned.

if (card_from_state)
if (card_ptr != s_memory_cards[i].get())
{
if (s_memory_cards[i])
{
if (s_memory_cards[i]->GetData() == card_from_state->GetData())
if (s_memory_cards[i]->GetData() == card_ptr->GetData())
{
card_from_state->SetFilename(s_memory_cards[i]->GetFilename());
s_memory_cards[i] = std::move(card_from_state);
Log_DevFmt("Card {} data matches, copying state", i + 1u);
s_memory_cards[i]->CopyState(card_ptr);
}
else
{
Host::AddFormattedOSDMessage(
20.0f,
TRANSLATE("OSDMessage",
"Memory card %u from save state does match current card data. Simulating replugging."),
i + 1u);
Host::AddIconOSDMessage(
fmt::format("card_load_warning_{}", i), ICON_FA_SD_CARD,
fmt::format(
TRANSLATE_FS("OSDMessage",
"Memory card {} from save state does match current card data. Simulating replugging."),
i + 1u),
Host::OSD_WARNING_DURATION);

// this is a potentially serious issue - some games cache info from memcards and jumping around
// with savestates can lead to card corruption on the next save attempts (and may not be obvious
// until much later). One workaround is to forcibly eject the card for 30+ frames, long enough
// for the game to decide it was removed and purge its cache. Once implemented, this could be
// described as deferred re-plugging in the log.

Log_WarningPrintf("Memory card %u data mismatch. Using current data via instant-replugging.", i + 1u);
Log_WarningFmt("Memory card {} data mismatch. Using current data via instant-replugging.", i + 1u);
s_memory_cards[i]->Reset();
}
}
else
{
Host::AddFormattedOSDMessage(
20.0f, TRANSLATE("OSDMessage", "Memory card %u present in save state but not in system. Ignoring card."),
i + 1u);
Host::AddIconOSDMessage(
fmt::format("card_load_warning_{}", i), ICON_FA_SD_CARD,
fmt::format(
TRANSLATE_FS("OSDMessage", "Memory card {} present in save state but not in system. Ignoring card."), i + 1u),
Host::OSD_ERROR_DURATION);
}

return true;
Expand All @@ -325,16 +331,21 @@ 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."),
i + 1u);
Host::AddIconOSDMessage(
fmt::format("card_load_warning_{}", i), ICON_FA_SD_CARD,
fmt::format(
TRANSLATE_FS("OSDMessage", "Memory card {} present in system but not in save state. Removing card."), i + 1u),
Host::OSD_ERROR_DURATION);
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."),
i + 1u);
Host::AddIconOSDMessage(
fmt::format("card_load_warning_{}", i), ICON_FA_SD_CARD,
fmt::format(
TRANSLATE_FS("OSDMessage", "Memory card {} present in system but not in save state. Replugging card."),
i + 1u),
Host::OSD_WARNING_DURATION);
s_memory_cards[i]->Reset();
}
}
Expand Down

0 comments on commit c41563c

Please sign in to comment.