Skip to content
Permalink
Browse files
Merge pull request #8879 from AdmiralCurtiss/gci-folder-savestates
Keep memory card header and use it to reinitialize GCI folders on mismatch. (Fix savestate with GCI folders)
  • Loading branch information
delroth committed Jun 24, 2020
2 parents eb278b6 + 476c959 commit cf36877
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 82 deletions.
@@ -9,17 +9,21 @@

#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/IniFile.h"

#include "Core/ConfigManager.h"
#include "Core/CoreTiming.h"
#include "Core/HW/EXI/EXI_Channel.h"
#include "Core/HW/EXI/EXI_DeviceMemoryCard.h"
#include "Core/HW/GCMemcard/GCMemcard.h"
#include "Core/HW/MMIO.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/Sram.h"
#include "Core/HW/SystemTimers.h"
#include "Core/Movie.h"

#include "DiscIO/Enums.h"

Sram g_SRAM;
bool g_SRAM_netplay_initialized = false;

@@ -69,8 +73,29 @@ void Init()
}

CEXIMemoryCard::Init();
for (u32 i = 0; i < MAX_EXI_CHANNELS; i++)
g_Channels[i] = std::make_unique<CEXIChannel>(i);

{
bool use_memcard_251;
IniFile gameIni = SConfig::GetInstance().LoadGameIni();
gameIni.GetOrCreateSection("Core")->Get("MemoryCard251", &use_memcard_251, false);
const u16 size_mbits =
use_memcard_251 ? Memcard::MBIT_SIZE_MEMORY_CARD_251 : Memcard::MBIT_SIZE_MEMORY_CARD_2043;
const bool shift_jis =
SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region) == DiscIO::Region::NTSC_J;
const CardFlashId& flash_id = g_SRAM.settings_ex.flash_id[Memcard::SLOT_A];
const u32 rtc_bias = g_SRAM.settings.rtc_bias;
const u32 sram_language = static_cast<u32>(g_SRAM.settings.language);
const u64 format_time =
Common::Timer::GetLocalTimeSinceJan1970() - ExpansionInterface::CEXIIPL::GC_EPOCH;

for (u32 i = 0; i < MAX_EXI_CHANNELS; i++)
{
Memcard::HeaderData header_data;
Memcard::InitializeHeaderData(&header_data, flash_id, size_mbits, shift_jis, rtc_bias,
sram_language, format_time + i);
g_Channels[i] = std::make_unique<CEXIChannel>(i, header_data);
}
}

for (int i = 0; i < MAX_MEMORYCARD_SLOTS; i++)
AddMemoryCards(i);
@@ -126,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)
@@ -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);

@@ -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
{
@@ -22,15 +25,16 @@ enum
EXI_READWRITE
};

CEXIChannel::CEXIChannel(u32 channel_id) : m_channel_id(channel_id)
CEXIChannel::CEXIChannel(u32 channel_id, const Memcard::HeaderData& memcard_header_data)
: m_channel_id(channel_id), m_memcard_header_data(memcard_header_data)
{
if (m_channel_id == 0 || m_channel_id == 1)
m_status.EXTINT = 1;
if (m_channel_id == 1)
m_status.CHIP_SELECT = 1;

for (auto& device : m_devices)
device = EXIDevice_Create(EXIDEVICE_NONE, m_channel_id);
device = EXIDevice_Create(EXIDEVICE_NONE, m_channel_id, m_memcard_header_data);
}

CEXIChannel::~CEXIChannel()
@@ -166,14 +170,19 @@ void CEXIChannel::RemoveDevices()

void CEXIChannel::AddDevice(const TEXIDevices device_type, const int device_num)
{
AddDevice(EXIDevice_Create(device_type, m_channel_id), device_num);
AddDevice(EXIDevice_Create(device_type, m_channel_id, m_memcard_header_data), device_num);
}

void CEXIChannel::AddDevice(std::unique_ptr<IEXIDevice> device, const int device_num,
bool notify_presence_changed)
{
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
m_devices[device_num] = std::move(device);

@@ -230,6 +239,9 @@ void CEXIChannel::DoState(PointerWrap& p)
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)
{
std::unique_ptr<IEXIDevice>& device = m_devices[device_index];
@@ -242,10 +254,33 @@ void CEXIChannel::DoState(PointerWrap& p)
}
else
{
std::unique_ptr<IEXIDevice> save_device = EXIDevice_Create(type, m_channel_id);
std::unique_ptr<IEXIDevice> save_device =
EXIDevice_Create(type, m_channel_id, m_memcard_header_data);
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);
}
}
}

@@ -6,8 +6,11 @@

#include <array>
#include <memory>

#include "Common/CommonTypes.h"

#include "Core/HW/GCMemcard/GCMemcard.h"

class PointerWrap;

namespace MMIO
@@ -23,7 +26,7 @@ enum TEXIDevices : int;
class CEXIChannel
{
public:
explicit CEXIChannel(u32 channel_id);
explicit CEXIChannel(u32 channel_id, const Memcard::HeaderData& memcard_header_data);
~CEXIChannel();

// get device
@@ -106,6 +109,12 @@ class CEXIChannel
UEXI_CONTROL m_control;
u32 m_imm_data = 0;

// This data is needed in order to reinitialize a GCI folder memory card when switching between
// GCI folder and other devices in the memory card slot or after loading a savestate. Even though
// this data is only vaguely related to the EXI_Channel, this seems to be the best place to store
// it, as this class creates the CEXIMemoryCard instances.
Memcard::HeaderData m_memcard_header_data;

// Devices
enum
{
@@ -102,7 +102,8 @@ void IEXIDevice::TransferByte(u8& byte)
}

// F A C T O R Y
std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, const int channel_num)
std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, const int channel_num,
const Memcard::HeaderData& memcard_header_data)
{
std::unique_ptr<IEXIDevice> result;

@@ -116,7 +117,7 @@ std::unique_ptr<IEXIDevice> EXIDevice_Create(const TEXIDevices device_type, cons
case EXIDEVICE_MEMORYCARDFOLDER:
{
bool gci_folder = (device_type == EXIDEVICE_MEMORYCARDFOLDER);
result = std::make_unique<CEXIMemoryCard>(channel_num, gci_folder);
result = std::make_unique<CEXIMemoryCard>(channel_num, gci_folder, memcard_header_data);
break;
}
case EXIDEVICE_MASKROM:
@@ -9,6 +9,11 @@

class PointerWrap;

namespace Memcard
{
struct HeaderData;
}

namespace ExpansionInterface
{
enum TEXIDevices : int
@@ -65,5 +70,6 @@ class IEXIDevice
virtual void TransferByte(u8& byte);
};

std::unique_ptr<IEXIDevice> EXIDevice_Create(TEXIDevices device_type, int channel_num);
std::unique_ptr<IEXIDevice> EXIDevice_Create(TEXIDevices device_type, int channel_num,
const Memcard::HeaderData& memcard_header_data);
} // namespace ExpansionInterface
@@ -106,7 +106,9 @@ void CEXIMemoryCard::Shutdown()
s_et_transfer_complete.fill(nullptr);
}

CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder) : card_index(index)
CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder,
const Memcard::HeaderData& header_data)
: card_index(index)
{
ASSERT_MSG(EXPANSIONINTERFACE, static_cast<std::size_t>(index) < s_et_cmd_done.size(),
"Trying to create invalid memory card index %d.", index);
@@ -132,25 +134,13 @@ CEXIMemoryCard::CEXIMemoryCard(const int index, bool gciFolder) : card_index(ind
// card_id = 0xc243;
card_id = 0xc221; // It's a Nintendo brand memcard

// The following games have issues with memory cards bigger than 16Mb
// Darkened Skye GDQE6S GDQP6S
// WTA Tour Tennis GWTEA4 GWTJA4 GWTPA4
// Disney Sports : Skate Boarding GDXEA4 GDXPA4 GDXJA4
// Disney Sports : Soccer GDKEA4
// Wallace and Gromit in Pet Zoo GWLE6L GWLX6L
// Use a 16Mb (251 block) memory card for these games
bool useMC251;
IniFile gameIni = SConfig::GetInstance().LoadGameIni();
gameIni.GetOrCreateSection("Core")->Get("MemoryCard251", &useMC251, false);
u16 sizeMb = useMC251 ? Memcard::MBIT_SIZE_MEMORY_CARD_251 : Memcard::MBIT_SIZE_MEMORY_CARD_2043;

if (gciFolder)
{
SetupGciFolder(sizeMb);
SetupGciFolder(header_data);
}
else
{
SetupRawMemcard(sizeMb);
SetupRawMemcard(header_data.m_size_mb);
}

memory_card_size = memorycard->GetCardId() * SIZE_TO_Mb;
@@ -185,7 +175,7 @@ CEXIMemoryCard::GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_fo
return {std::move(path), !use_movie_folder};
}

void CEXIMemoryCard::SetupGciFolder(u16 sizeMb)
void CEXIMemoryCard::SetupGciFolder(const Memcard::HeaderData& header_data)
{
const std::string& game_id = SConfig::GetInstance().GetGameID();
u32 CurrentGameId = 0;
@@ -195,8 +185,7 @@ void CEXIMemoryCard::SetupGciFolder(u16 sizeMb)
CurrentGameId = Common::swap32(reinterpret_cast<const u8*>(game_id.c_str()));
}

const bool shift_jis =
SConfig::ToGameCubeRegion(SConfig::GetInstance().m_region) == DiscIO::Region::NTSC_J;
const bool shift_jis = header_data.m_encoding != 0;

const auto [strDirectoryName, migrate] = GetGCIFolderPath(card_index, AllowMovieFolder::Yes);

@@ -228,8 +217,8 @@ void CEXIMemoryCard::SetupGciFolder(u16 sizeMb)
}
}

memorycard = std::make_unique<GCMemcardDirectory>(strDirectoryName + DIR_SEP, card_index, sizeMb,
shift_jis, CurrentGameId);
memorycard = std::make_unique<GCMemcardDirectory>(strDirectoryName + DIR_SEP, card_index,
header_data, CurrentGameId);
}

void CEXIMemoryCard::SetupRawMemcard(u16 sizeMb)
@@ -14,6 +14,11 @@
class MemoryCardBase;
class PointerWrap;

namespace Memcard
{
struct HeaderData;
}

namespace ExpansionInterface
{
enum class AllowMovieFolder
@@ -25,7 +30,7 @@ enum class AllowMovieFolder
class CEXIMemoryCard : public IEXIDevice
{
public:
CEXIMemoryCard(const int index, bool gciFolder);
CEXIMemoryCard(const int index, bool gciFolder, const Memcard::HeaderData& header_data);
virtual ~CEXIMemoryCard();
void SetCS(int cs) override;
bool IsInterruptSet() override;
@@ -46,7 +51,7 @@ class CEXIMemoryCard : public IEXIDevice
GetGCIFolderPath(int card_index, AllowMovieFolder allow_movie_folder);

private:
void SetupGciFolder(u16 sizeMb);
void SetupGciFolder(const Memcard::HeaderData& header_data);
void SetupRawMemcard(u16 sizeMb);
static void EventCompleteFindInstance(u64 userdata,
std::function<void(CEXIMemoryCard*)> callback);

0 comments on commit cf36877

Please sign in to comment.