diff --git a/Source/Core/Core/HW/EXI/EXI.cpp b/Source/Core/Core/HW/EXI/EXI.cpp index 7616e2f0af..0c11ad8e05 100644 --- a/Source/Core/Core/HW/EXI/EXI.cpp +++ b/Source/Core/Core/HW/EXI/EXI.cpp @@ -151,16 +151,16 @@ static void ChangeDeviceCallback(u64 userdata, s64 cyclesLate) g_Channels.at(channel)->AddDevice((TEXIDevices)type, num); } -void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num) +void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num, + CoreTiming::FromThread from_thread) { - // Called from GUI, so we need to use FromThread::NON_CPU. // Let the hardware see no device for 1 second CoreTiming::ScheduleEvent(0, changeDevice, ((u64)channel << 32) | ((u64)EXIDEVICE_NONE << 16) | device_num, - CoreTiming::FromThread::NON_CPU); + from_thread); CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), changeDevice, ((u64)channel << 32) | ((u64)device_type << 16) | device_num, - CoreTiming::FromThread::NON_CPU); + from_thread); } CEXIChannel* GetChannel(u32 index) diff --git a/Source/Core/Core/HW/EXI/EXI.h b/Source/Core/Core/HW/EXI/EXI.h index 2d1890bd10..27e806d472 100644 --- a/Source/Core/Core/HW/EXI/EXI.h +++ b/Source/Core/Core/HW/EXI/EXI.h @@ -5,6 +5,7 @@ #pragma once #include "Common/CommonTypes.h" +#include "Core/CoreTiming.h" class PointerWrap; @@ -39,7 +40,8 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base); void UpdateInterrupts(); void ScheduleUpdateInterrupts(CoreTiming::FromThread from, int cycles_late); -void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num); +void ChangeDevice(const u8 channel, const TEXIDevices device_type, const u8 device_num, + CoreTiming::FromThread from_thread = CoreTiming::FromThread::NON_CPU); CEXIChannel* GetChannel(u32 index); diff --git a/Source/Core/Core/HW/EXI/EXI_Channel.cpp b/Source/Core/Core/HW/EXI/EXI_Channel.cpp index 1f0e3480c6..266fa9b73f 100644 --- a/Source/Core/Core/HW/EXI/EXI_Channel.cpp +++ b/Source/Core/Core/HW/EXI/EXI_Channel.cpp @@ -9,9 +9,12 @@ #include "Common/Assert.h" #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" + +#include "Core/CoreTiming.h" #include "Core/HW/EXI/EXI.h" #include "Core/HW/EXI/EXI_Device.h" #include "Core/HW/MMIO.h" +#include "Core/Movie.h" namespace ExpansionInterface { @@ -175,6 +178,11 @@ void CEXIChannel::AddDevice(std::unique_ptr device, const int device { DEBUG_ASSERT(device_num < NUM_DEVICES); + INFO_LOG(EXPANSIONINTERFACE, + "Changing EXI channel %d, device %d to type %d (notify software: %s)", + static_cast(m_channel_id), device_num, static_cast(device->m_device_type), + notify_presence_changed ? "true" : "false"); + // Replace it with the new one m_devices[device_num] = std::move(device); @@ -230,6 +238,8 @@ void CEXIChannel::DoState(PointerWrap& p) p.Do(m_dma_length); p.Do(m_control); p.Do(m_imm_data); + + Memcard::HeaderData old_header_data = m_memcard_header_data; p.DoPOD(m_memcard_header_data); for (int device_index = 0; device_index < NUM_DEVICES; ++device_index) @@ -249,6 +259,28 @@ void CEXIChannel::DoState(PointerWrap& p) save_device->DoState(p); AddDevice(std::move(save_device), device_index, false); } + + if (type == EXIDEVICE_MEMORYCARDFOLDER && old_header_data != m_memcard_header_data && + !Movie::IsMovieActive()) + { + // We have loaded a savestate that has a GCI folder memcard that is different to the virtual + // card that is currently active. In order to prevent the game from recognizing this card as a + // 'different' memory card and preventing saving on it, we need to reinitialize the GCI folder + // card here with the loaded header data. + // We're intentionally calling ExpansionInterface::ChangeDevice() here instead of changing it + // directly so we don't switch immediately but after a delay, as if changed in the GUI. This + // should prevent games from assuming any stale data about the memory card, such as location + // of the individual save blocks, which may be different on the reinitialized card. + // Additionally, we immediately force the memory card to None so that any 'in-flight' writes + // (if someone managed to savestate while saving...) don't happen to hit the card. + // TODO: It might actually be enough to just switch to the card with the + // notify_presence_changed flag set to true? Not sure how software behaves if the previous and + // the new device type are identical in this case. I assume there is a reason we have this + // grace period when switching in the GUI. + AddDevice(EXIDEVICE_NONE, device_index); + ExpansionInterface::ChangeDevice(m_channel_id, EXIDEVICE_MEMORYCARDFOLDER, device_index, + CoreTiming::FromThread::CPU); + } } } diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp index ee71f549aa..aec7f97105 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp @@ -1576,6 +1576,17 @@ void InitializeHeaderData(HeaderData* data, const CardFlashId& flash_id, u16 siz data->m_device_id = 0; } +bool operator==(const HeaderData& lhs, const HeaderData& rhs) +{ + static_assert(std::is_trivially_copyable_v); + return std::memcmp(&lhs, &rhs, sizeof(HeaderData)) == 0; +} + +bool operator!=(const HeaderData& lhs, const HeaderData& rhs) +{ + return !(lhs == rhs); +} + Header::Header(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, u32 sram_language, u64 format_time) { diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.h b/Source/Core/Core/HW/GCMemcard/GCMemcard.h index 8e86e69944..7ec6e912bd 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.h @@ -206,6 +206,9 @@ static_assert(std::is_trivially_copyable_v); void InitializeHeaderData(HeaderData* data, const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, u32 sram_language, u64 format_time); +bool operator==(const HeaderData& lhs, const HeaderData& rhs); +bool operator!=(const HeaderData& lhs, const HeaderData& rhs); + struct Header { HeaderData m_data;