EXI: When loading a savestate with a mismatching GCI folder memory card, reinizialize it with the header from the savestate to let a game still recognize it as the same card.
This commit is contained in:
parent
8b13e1882a
commit
476c95900d
|
@ -151,16 +151,16 @@ static void ChangeDeviceCallback(u64 userdata, s64 cyclesLate)
|
||||||
g_Channels.at(channel)->AddDevice((TEXIDevices)type, num);
|
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
|
// Let the hardware see no device for 1 second
|
||||||
CoreTiming::ScheduleEvent(0, changeDevice,
|
CoreTiming::ScheduleEvent(0, changeDevice,
|
||||||
((u64)channel << 32) | ((u64)EXIDEVICE_NONE << 16) | device_num,
|
((u64)channel << 32) | ((u64)EXIDEVICE_NONE << 16) | device_num,
|
||||||
CoreTiming::FromThread::NON_CPU);
|
from_thread);
|
||||||
CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), changeDevice,
|
CoreTiming::ScheduleEvent(SystemTimers::GetTicksPerSecond(), changeDevice,
|
||||||
((u64)channel << 32) | ((u64)device_type << 16) | device_num,
|
((u64)channel << 32) | ((u64)device_type << 16) | device_num,
|
||||||
CoreTiming::FromThread::NON_CPU);
|
from_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
CEXIChannel* GetChannel(u32 index)
|
CEXIChannel* GetChannel(u32 index)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Core/CoreTiming.h"
|
||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
|
||||||
|
@ -39,7 +40,8 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
|
||||||
void UpdateInterrupts();
|
void UpdateInterrupts();
|
||||||
void ScheduleUpdateInterrupts(CoreTiming::FromThread from, int cycles_late);
|
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);
|
CEXIChannel* GetChannel(u32 index);
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,12 @@
|
||||||
#include "Common/Assert.h"
|
#include "Common/Assert.h"
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/HW/EXI/EXI.h"
|
#include "Core/HW/EXI/EXI.h"
|
||||||
#include "Core/HW/EXI/EXI_Device.h"
|
#include "Core/HW/EXI/EXI_Device.h"
|
||||||
#include "Core/HW/MMIO.h"
|
#include "Core/HW/MMIO.h"
|
||||||
|
#include "Core/Movie.h"
|
||||||
|
|
||||||
namespace ExpansionInterface
|
namespace ExpansionInterface
|
||||||
{
|
{
|
||||||
|
@ -175,6 +178,11 @@ void CEXIChannel::AddDevice(std::unique_ptr<IEXIDevice> device, const int device
|
||||||
{
|
{
|
||||||
DEBUG_ASSERT(device_num < NUM_DEVICES);
|
DEBUG_ASSERT(device_num < NUM_DEVICES);
|
||||||
|
|
||||||
|
INFO_LOG(EXPANSIONINTERFACE,
|
||||||
|
"Changing EXI channel %d, device %d to type %d (notify software: %s)",
|
||||||
|
static_cast<int>(m_channel_id), device_num, static_cast<int>(device->m_device_type),
|
||||||
|
notify_presence_changed ? "true" : "false");
|
||||||
|
|
||||||
// Replace it with the new one
|
// Replace it with the new one
|
||||||
m_devices[device_num] = std::move(device);
|
m_devices[device_num] = std::move(device);
|
||||||
|
|
||||||
|
@ -230,6 +238,8 @@ void CEXIChannel::DoState(PointerWrap& p)
|
||||||
p.Do(m_dma_length);
|
p.Do(m_dma_length);
|
||||||
p.Do(m_control);
|
p.Do(m_control);
|
||||||
p.Do(m_imm_data);
|
p.Do(m_imm_data);
|
||||||
|
|
||||||
|
Memcard::HeaderData old_header_data = m_memcard_header_data;
|
||||||
p.DoPOD(m_memcard_header_data);
|
p.DoPOD(m_memcard_header_data);
|
||||||
|
|
||||||
for (int device_index = 0; device_index < NUM_DEVICES; ++device_index)
|
for (int device_index = 0; device_index < NUM_DEVICES; ++device_index)
|
||||||
|
@ -249,6 +259,28 @@ void CEXIChannel::DoState(PointerWrap& p)
|
||||||
save_device->DoState(p);
|
save_device->DoState(p);
|
||||||
AddDevice(std::move(save_device), device_index, false);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1576,6 +1576,17 @@ void InitializeHeaderData(HeaderData* data, const CardFlashId& flash_id, u16 siz
|
||||||
data->m_device_id = 0;
|
data->m_device_id = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool operator==(const HeaderData& lhs, const HeaderData& rhs)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivially_copyable_v<HeaderData>);
|
||||||
|
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,
|
Header::Header(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias,
|
||||||
u32 sram_language, u64 format_time)
|
u32 sram_language, u64 format_time)
|
||||||
{
|
{
|
||||||
|
|
|
@ -206,6 +206,9 @@ static_assert(std::is_trivially_copyable_v<HeaderData>);
|
||||||
void InitializeHeaderData(HeaderData* data, const CardFlashId& flash_id, u16 size_mbits,
|
void InitializeHeaderData(HeaderData* data, const CardFlashId& flash_id, u16 size_mbits,
|
||||||
bool shift_jis, u32 rtc_bias, u32 sram_language, u64 format_time);
|
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
|
struct Header
|
||||||
{
|
{
|
||||||
HeaderData m_data;
|
HeaderData m_data;
|
||||||
|
|
Loading…
Reference in New Issue