From 07bd2005965200f02c619b1a4a6731ba0df18827 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 15 Jun 2020 22:11:02 +0200 Subject: [PATCH 1/5] GCMemcard: Remove dependency on g_SRAM and force the caller to provide the relevant values instead. --- Source/Core/Core/HW/GCMemcard/GCMemcard.cpp | 47 ++++++++++++------- Source/Core/Core/HW/GCMemcard/GCMemcard.h | 19 +++++--- .../Core/HW/GCMemcard/GCMemcardDirectory.cpp | 10 +++- .../Core/Core/HW/GCMemcard/GCMemcardRaw.cpp | 19 ++++++-- .../DolphinQt/GCMemcardCreateNewDialog.cpp | 13 ++++- 5 files changed, 79 insertions(+), 29 deletions(-) diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp index 11676f4faf..cfa0d5ad47 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp @@ -64,14 +64,16 @@ GCMemcard::GCMemcard() { } -std::optional GCMemcard::Create(std::string filename, u16 size_mbits, bool shift_jis) +std::optional GCMemcard::Create(std::string filename, const CardFlashId& flash_id, + u16 size_mbits, bool shift_jis, u32 rtc_bias, + u32 sram_language, u64 format_time) { GCMemcard card; card.m_filename = std::move(filename); // TODO: Format() not only formats the card but also writes it to disk at m_filename. // Those tasks should probably be separated. - if (!card.Format(shift_jis, size_mbits)) + if (!card.Format(flash_id, size_mbits, shift_jis, rtc_bias, sram_language, format_time)) return std::nullopt; return std::move(card); @@ -1367,29 +1369,32 @@ std::optional> GCMemcard::ReadAnimRGBA return output; } -bool GCMemcard::Format(u8* card_data, bool shift_jis, u16 SizeMb) +bool GCMemcard::Format(u8* card_data, const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, + u32 rtc_bias, u32 sram_language, u64 format_time) { if (!card_data) return false; memset(card_data, 0xFF, BLOCK_SIZE * 3); memset(card_data + BLOCK_SIZE * 3, 0, BLOCK_SIZE * 2); - *((Header*)card_data) = Header(SLOT_A, SizeMb, shift_jis); + *((Header*)card_data) = + Header(flash_id, size_mbits, shift_jis, rtc_bias, sram_language, format_time); *((Directory*)(card_data + BLOCK_SIZE)) = Directory(); *((Directory*)(card_data + BLOCK_SIZE * 2)) = Directory(); - *((BlockAlloc*)(card_data + BLOCK_SIZE * 3)) = BlockAlloc(SizeMb); - *((BlockAlloc*)(card_data + BLOCK_SIZE * 4)) = BlockAlloc(SizeMb); + *((BlockAlloc*)(card_data + BLOCK_SIZE * 3)) = BlockAlloc(size_mbits); + *((BlockAlloc*)(card_data + BLOCK_SIZE * 4)) = BlockAlloc(size_mbits); return true; } -bool GCMemcard::Format(bool shift_jis, u16 SizeMb) +bool GCMemcard::Format(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, + u32 sram_language, u64 format_time) { - m_header_block = Header(SLOT_A, SizeMb, shift_jis); + m_header_block = Header(flash_id, size_mbits, shift_jis, rtc_bias, sram_language, format_time); m_directory_blocks[0] = m_directory_blocks[1] = Directory(); - m_bat_blocks[0] = m_bat_blocks[1] = BlockAlloc(SizeMb); + m_bat_blocks[0] = m_bat_blocks[1] = BlockAlloc(size_mbits); - m_size_mb = SizeMb; + m_size_mb = size_mbits; m_size_blocks = (u32)m_size_mb * MBIT_TO_BLOCKS; m_data_blocks.clear(); m_data_blocks.resize(m_size_blocks - MC_FST_BLOCKS); @@ -1538,25 +1543,33 @@ void GCMBlock::Erase() memset(m_block.data(), 0xFF, m_block.size()); } -Header::Header(int slot, u16 size_mbits, bool shift_jis) +Header::Header() +{ + static_assert(std::is_trivially_copyable_v
); + std::memset(this, 0xFF, BLOCK_SIZE); +} + +Header::Header(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, + u32 sram_language, u64 format_time) { // Nintendo format algorithm. // Constants are fixed by the GC SDK // Changing the constants will break memory card support - memset(this, 0xFF, BLOCK_SIZE); + static_assert(std::is_trivially_copyable_v
); + std::memset(this, 0xFF, BLOCK_SIZE); m_size_mb = size_mbits; m_encoding = shift_jis ? 1 : 0; - u64 rand = Common::Timer::GetLocalTimeSinceJan1970() - ExpansionInterface::CEXIIPL::GC_EPOCH; - m_format_time = rand; + m_format_time = format_time; + u64 rand = format_time; for (int i = 0; i < 12; i++) { rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16); - m_serial[i] = (u8)(g_SRAM.settings_ex.flash_id[slot][i] + (u32)rand); + m_serial[i] = (u8)(flash_id[i] + (u32)rand); rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16); rand &= (u64)0x0000000000007fffULL; } - m_sram_bias = g_SRAM.settings.rtc_bias; - m_sram_language = static_cast(g_SRAM.settings.language); + m_sram_bias = rtc_bias; + m_sram_language = sram_language; // TODO: determine the purpose of m_unknown_2 // 1 works for slot A, 0 works for both slot A and slot B memset(m_unknown_2.data(), 0, diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.h b/Source/Core/Core/HW/GCMemcard/GCMemcard.h index 71b593134d..2d023e4a1a 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.h @@ -213,8 +213,12 @@ struct Header // 0x1e00 bytes at 0x0200: Unused (0xff) std::array m_unused_2; - explicit Header(int slot = 0, u16 size_mbits = MBIT_SIZE_MEMORY_CARD_2043, - bool shift_jis = false); + // initialize an unformatted header block + explicit Header(); + + // initialize a formatted header block + explicit Header(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, + u32 sram_language, u64 format_time); // Calculates the card serial numbers used for encrypting some save files. std::pair CalculateSerial() const; @@ -401,7 +405,9 @@ private: void UpdateBat(const BlockAlloc& bat); public: - static std::optional Create(std::string filename, u16 size_mbits, bool shift_jis); + static std::optional Create(std::string filename, const CardFlashId& flash_id, + u16 size_mbits, bool shift_jis, u32 rtc_bias, + u32 sram_language, u64 format_time); static std::pair> Open(std::string filename); @@ -413,9 +419,10 @@ public: bool IsValid() const { return m_valid; } bool IsShiftJIS() const; bool Save(); - bool Format(bool shift_jis = false, u16 SizeMb = MBIT_SIZE_MEMORY_CARD_2043); - static bool Format(u8* card_data, bool shift_jis = false, - u16 SizeMb = MBIT_SIZE_MEMORY_CARD_2043); + bool Format(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, + u32 sram_language, u64 format_time); + static bool Format(u8* card_data, const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, + u32 rtc_bias, u32 sram_language, u64 format_time); static s32 FZEROGX_MakeSaveGameValid(const Header& cardheader, const DEntry& direntry, std::vector& FileBuffer); static s32 PSO_MakeSaveGameValid(const Header& cardheader, const DEntry& direntry, diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index 2cfb80b47b..398e5b031c 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -26,9 +26,13 @@ #include "Common/MsgHandler.h" #include "Common/StringUtil.h" #include "Common/Thread.h" +#include "Common/Timer.h" + #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" +#include "Core/HW/EXI/EXI_DeviceIPL.h" +#include "Core/HW/Sram.h" #include "Core/NetPlayProto.h" static const char* MC_HDR = "MC_SYSTEM_AREA"; @@ -148,8 +152,10 @@ std::vector GCMemcardDirectory::GetFileNamesForGameID(const std::st GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u16 size_mbits, bool shift_jis, int game_id) : MemoryCardBase(slot, size_mbits), m_game_id(game_id), m_last_block(-1), - m_hdr(slot, size_mbits, shift_jis), m_bat1(size_mbits), m_saves(0), - m_save_directory(directory), m_exiting(false) + m_hdr(g_SRAM.settings_ex.flash_id[slot], size_mbits, shift_jis, g_SRAM.settings.rtc_bias, + static_cast(g_SRAM.settings.language), + Common::Timer::GetLocalTimeSinceJan1970() - ExpansionInterface::CEXIIPL::GC_EPOCH), + m_bat1(size_mbits), m_saves(0), m_save_directory(directory), m_exiting(false) { // Use existing header data if available { diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp index 90d715eedd..7e8a472c12 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardRaw.cpp @@ -22,9 +22,13 @@ #include "Common/MsgHandler.h" #include "Common/StringUtil.h" #include "Common/Thread.h" +#include "Common/Timer.h" + #include "Core/ConfigManager.h" #include "Core/Core.h" +#include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/GCMemcard/GCMemcard.h" +#include "Core/HW/Sram.h" #define SIZE_TO_Mb (1024 * 8 * 16) #define MC_HDR_SIZE 0xA000 @@ -51,9 +55,18 @@ MemoryCard::MemoryCard(const std::string& filename, int card_index, u16 size_mbi m_memory_card_size = size_mbits * SIZE_TO_Mb; m_memcard_data = std::make_unique(m_memory_card_size); - // Fills in MC_HDR_SIZE bytes - Memcard::GCMemcard::Format(&m_memcard_data[0], m_filename.find(".JAP.raw") != std::string::npos, - size_mbits); + + // Fills in the first 5 blocks (MC_HDR_SIZE bytes) + const CardFlashId& flash_id = g_SRAM.settings_ex.flash_id[Memcard::SLOT_A]; + const bool shift_jis = m_filename.find(".JAP.raw") != std::string::npos; + const u32 rtc_bias = g_SRAM.settings.rtc_bias; + const u32 sram_language = static_cast(g_SRAM.settings.language); + const u64 format_time = + Common::Timer::GetLocalTimeSinceJan1970() - ExpansionInterface::CEXIIPL::GC_EPOCH; + Memcard::GCMemcard::Format(&m_memcard_data[0], flash_id, size_mbits, shift_jis, rtc_bias, + sram_language, format_time); + + // Fills in the remaining blocks memset(&m_memcard_data[MC_HDR_SIZE], 0xFF, m_memory_card_size - MC_HDR_SIZE); INFO_LOG(EXPANSIONINTERFACE, "No memory card found. A new one was created instead."); diff --git a/Source/Core/DolphinQt/GCMemcardCreateNewDialog.cpp b/Source/Core/DolphinQt/GCMemcardCreateNewDialog.cpp index 36a12627f4..b54766d157 100644 --- a/Source/Core/DolphinQt/GCMemcardCreateNewDialog.cpp +++ b/Source/Core/DolphinQt/GCMemcardCreateNewDialog.cpp @@ -14,8 +14,11 @@ #include "Common/FileUtil.h" #include "Common/MsgHandler.h" +#include "Common/Timer.h" +#include "Core/HW/EXI/EXI_DeviceIPL.h" #include "Core/HW/GCMemcard/GCMemcard.h" +#include "Core/HW/Sram.h" GCMemcardCreateNewDialog::GCMemcardCreateNewDialog(QWidget* parent) : QDialog(parent) { @@ -74,8 +77,16 @@ bool GCMemcardCreateNewDialog::CreateCard() if (path.isEmpty()) return false; + // TODO: The dependency on g_SRAM here is sketchy. We should instead use sensible default values. + 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(g_SRAM.settings.language); + const u64 format_time = + Common::Timer::GetLocalTimeSinceJan1970() - ExpansionInterface::CEXIIPL::GC_EPOCH; + const std::string p = path.toStdString(); - auto memcard = Memcard::GCMemcard::Create(p, size, is_shift_jis); + auto memcard = Memcard::GCMemcard::Create(p, flash_id, size, is_shift_jis, rtc_bias, + sram_language, format_time); if (memcard && memcard->Save()) { m_card_path = p; From cc52558c0e9b1941899c3353c67225e880eb5b9f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Wed, 17 Jun 2020 01:17:24 +0200 Subject: [PATCH 2/5] GCMemcard: Assert struct requirements in header. --- Source/Core/Core/HW/GCMemcard/GCMemcard.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.h b/Source/Core/Core/HW/GCMemcard/GCMemcard.h index 2d023e4a1a..7d3ca6404a 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.h @@ -166,6 +166,8 @@ struct GCMBlock void Erase(); std::array m_block; }; +static_assert(sizeof(GCMBlock) == BLOCK_SIZE); +static_assert(std::is_trivially_copyable_v); #pragma pack(push, 1) struct Header @@ -229,6 +231,7 @@ struct Header GCMemcardErrorCode CheckForErrors(u16 card_size_mbits) const; }; static_assert(sizeof(Header) == BLOCK_SIZE); +static_assert(std::is_trivially_copyable_v
); struct DEntry { @@ -306,6 +309,7 @@ struct DEntry Common::BigEndianValue m_comments_address; }; static_assert(sizeof(DEntry) == DENTRY_SIZE); +static_assert(std::is_trivially_copyable_v); struct BlockAlloc; @@ -341,6 +345,7 @@ struct Directory GCMemcardErrorCode CheckForErrorsWithBat(const BlockAlloc& bat) const; }; static_assert(sizeof(Directory) == BLOCK_SIZE); +static_assert(std::is_trivially_copyable_v); struct BlockAlloc { @@ -375,6 +380,7 @@ struct BlockAlloc GCMemcardErrorCode CheckForErrors(u16 size_mbits) const; }; static_assert(sizeof(BlockAlloc) == BLOCK_SIZE); +static_assert(std::is_trivially_copyable_v); #pragma pack(pop) class GCMemcard From e810d492f2cfa94b2dc61398e547113384c1c111 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 16 Jun 2020 00:33:25 +0200 Subject: [PATCH 3/5] GCMemcard: Split off HeaderData from Header to have a compact block of data for consistent initialization. --- Source/Core/Core/HW/GCMemcard/GCMemcard.cpp | 48 ++++++++++++------- Source/Core/Core/HW/GCMemcard/GCMemcard.h | 16 ++++++- .../Core/HW/GCMemcard/GCMemcardDirectory.cpp | 3 +- 3 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp index cfa0d5ad47..ee71f549aa 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.cpp @@ -304,7 +304,7 @@ void GCMemcard::UpdateBat(const BlockAlloc& bat) bool GCMemcard::IsShiftJIS() const { - return m_header_block.m_encoding != 0; + return m_header_block.m_data.m_encoding != 0; } bool GCMemcard::Save() @@ -1549,32 +1549,48 @@ Header::Header() std::memset(this, 0xFF, BLOCK_SIZE); } -Header::Header(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, - u32 sram_language, u64 format_time) +void InitializeHeaderData(HeaderData* data, const CardFlashId& flash_id, u16 size_mbits, + bool shift_jis, u32 rtc_bias, u32 sram_language, u64 format_time) { // Nintendo format algorithm. // Constants are fixed by the GC SDK // Changing the constants will break memory card support - static_assert(std::is_trivially_copyable_v
); - std::memset(this, 0xFF, BLOCK_SIZE); - m_size_mb = size_mbits; - m_encoding = shift_jis ? 1 : 0; - m_format_time = format_time; + data->m_size_mb = size_mbits; + data->m_encoding = shift_jis ? 1 : 0; + data->m_format_time = format_time; u64 rand = format_time; for (int i = 0; i < 12; i++) { rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16); - m_serial[i] = (u8)(flash_id[i] + (u32)rand); + data->m_serial[i] = (u8)(flash_id[i] + (u32)rand); rand = (((rand * (u64)0x0000000041c64e6dULL) + (u64)0x0000000000003039ULL) >> 16); rand &= (u64)0x0000000000007fffULL; } - m_sram_bias = rtc_bias; - m_sram_language = sram_language; + data->m_sram_bias = rtc_bias; + data->m_sram_language = sram_language; // TODO: determine the purpose of m_unknown_2 // 1 works for slot A, 0 works for both slot A and slot B - memset(m_unknown_2.data(), 0, - m_unknown_2.size()); // = _viReg[55]; static vu16* const _viReg = (u16*)0xCC002000; - m_device_id = 0; + std::memset( + data->m_unknown_2.data(), 0, + data->m_unknown_2.size()); // = _viReg[55]; static vu16* const _viReg = (u16*)0xCC002000; + data->m_device_id = 0; +} + +Header::Header(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, + u32 sram_language, u64 format_time) +{ + static_assert(std::is_trivially_copyable_v
); + std::memset(this, 0xFF, BLOCK_SIZE); + InitializeHeaderData(&m_data, flash_id, size_mbits, shift_jis, rtc_bias, sram_language, + format_time); + FixChecksums(); +} + +Header::Header(const HeaderData& data) +{ + static_assert(std::is_trivially_copyable_v
); + std::memset(this, 0xFF, BLOCK_SIZE); + m_data = data; FixChecksums(); } @@ -1622,7 +1638,7 @@ std::pair Header::CalculateChecksums() const std::array raw; memcpy(raw.data(), this, raw.size()); - constexpr size_t checksum_area_start = offsetof(Header, m_serial); + constexpr size_t checksum_area_start = offsetof(Header, m_data); constexpr size_t checksum_area_end = offsetof(Header, m_checksum); constexpr size_t checksum_area_size = checksum_area_end - checksum_area_start; return CalculateMemcardChecksums(&raw[checksum_area_start], checksum_area_size); @@ -1633,7 +1649,7 @@ GCMemcardErrorCode Header::CheckForErrors(u16 card_size_mbits) const GCMemcardErrorCode error_code; // total card size should match card size in header - if (m_size_mb != card_size_mbits) + if (m_data.m_size_mb != card_size_mbits) error_code.Set(GCMemcardValidityIssues::MISMATCHED_CARD_SIZE); // unused areas, should always be filled with 0xFF diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcard.h b/Source/Core/Core/HW/GCMemcard/GCMemcard.h index 7d3ca6404a..8e86e69944 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcard.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcard.h @@ -170,7 +170,9 @@ static_assert(sizeof(GCMBlock) == BLOCK_SIZE); static_assert(std::is_trivially_copyable_v); #pragma pack(push, 1) -struct Header +// split off from Header to have a small struct with all the data needed to regenerate the header +// for GCI folders +struct HeaderData { // NOTE: libogc refers to 'Serial' as the first 0x20 bytes of the header, // so the data from m_serial until m_unknown_2 (inclusive) @@ -198,6 +200,15 @@ struct Header // 2 bytes at 0x0024: Encoding (Windows-1252 or Shift JIS) Common::BigEndianValue m_encoding; +}; +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); + +struct Header +{ + HeaderData m_data; // 468 bytes at 0x0026: Unused (0xff) std::array m_unused_1; @@ -222,6 +233,9 @@ struct Header explicit Header(const CardFlashId& flash_id, u16 size_mbits, bool shift_jis, u32 rtc_bias, u32 sram_language, u64 format_time); + // initialize a header block from existing HeaderData + explicit Header(const HeaderData& data); + // Calculates the card serial numbers used for encrypting some save files. std::pair CalculateSerial() const; diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index 398e5b031c..9fe0c0dccb 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -201,7 +201,8 @@ GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u } // leave about 10% of free space on the card if possible - const int total_blocks = m_hdr.m_size_mb * Memcard::MBIT_TO_BLOCKS - Memcard::MC_FST_BLOCKS; + const int total_blocks = + m_hdr.m_data.m_size_mb * Memcard::MBIT_TO_BLOCKS - Memcard::MC_FST_BLOCKS; const int reserved_blocks = total_blocks / 10; // load files for other games From 8b13e1882a614c4c1a75ffc60344eafce8186bf9 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 16 Jun 2020 02:10:14 +0200 Subject: [PATCH 4/5] EXI: Store data for regenerating a GCI folder memory card header in EXI_Channel and pass it down to the memory card device. --- Source/Core/Core/HW/EXI/EXI.cpp | 29 +++++++++++++++++-- Source/Core/Core/HW/EXI/EXI_Channel.cpp | 11 ++++--- Source/Core/Core/HW/EXI/EXI_Channel.h | 11 ++++++- Source/Core/Core/HW/EXI/EXI_Device.cpp | 5 ++-- Source/Core/Core/HW/EXI/EXI_Device.h | 8 ++++- .../Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp | 29 ++++++------------- .../Core/Core/HW/EXI/EXI_DeviceMemoryCard.h | 9 ++++-- .../Core/HW/GCMemcard/GCMemcardDirectory.cpp | 12 ++++---- .../Core/HW/GCMemcard/GCMemcardDirectory.h | 4 +-- Source/Core/Core/State.cpp | 2 +- 10 files changed, 78 insertions(+), 42 deletions(-) diff --git a/Source/Core/Core/HW/EXI/EXI.cpp b/Source/Core/Core/HW/EXI/EXI.cpp index 131c69b092..7616e2f0af 100644 --- a/Source/Core/Core/HW/EXI/EXI.cpp +++ b/Source/Core/Core/HW/EXI/EXI.cpp @@ -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(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(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(i, header_data); + } + } for (int i = 0; i < MAX_MEMORYCARD_SLOTS; i++) AddMemoryCards(i); diff --git a/Source/Core/Core/HW/EXI/EXI_Channel.cpp b/Source/Core/Core/HW/EXI/EXI_Channel.cpp index b31d97fc54..1f0e3480c6 100644 --- a/Source/Core/Core/HW/EXI/EXI_Channel.cpp +++ b/Source/Core/Core/HW/EXI/EXI_Channel.cpp @@ -22,7 +22,8 @@ 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; @@ -30,7 +31,7 @@ CEXIChannel::CEXIChannel(u32 channel_id) : m_channel_id(channel_id) 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,7 +167,7 @@ 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 device, const int device_num, @@ -229,6 +230,7 @@ void CEXIChannel::DoState(PointerWrap& p) p.Do(m_dma_length); p.Do(m_control); p.Do(m_imm_data); + p.DoPOD(m_memcard_header_data); for (int device_index = 0; device_index < NUM_DEVICES; ++device_index) { @@ -242,7 +244,8 @@ void CEXIChannel::DoState(PointerWrap& p) } else { - std::unique_ptr save_device = EXIDevice_Create(type, m_channel_id); + std::unique_ptr save_device = + EXIDevice_Create(type, m_channel_id, m_memcard_header_data); save_device->DoState(p); AddDevice(std::move(save_device), device_index, false); } diff --git a/Source/Core/Core/HW/EXI/EXI_Channel.h b/Source/Core/Core/HW/EXI/EXI_Channel.h index b8fa6b1ffc..26b681d74b 100644 --- a/Source/Core/Core/HW/EXI/EXI_Channel.h +++ b/Source/Core/Core/HW/EXI/EXI_Channel.h @@ -6,8 +6,11 @@ #include #include + #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 @@ private: 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 { diff --git a/Source/Core/Core/HW/EXI/EXI_Device.cpp b/Source/Core/Core/HW/EXI/EXI_Device.cpp index be4a7029c8..83d1362be9 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.cpp +++ b/Source/Core/Core/HW/EXI/EXI_Device.cpp @@ -102,7 +102,8 @@ void IEXIDevice::TransferByte(u8& byte) } // F A C T O R Y -std::unique_ptr EXIDevice_Create(const TEXIDevices device_type, const int channel_num) +std::unique_ptr EXIDevice_Create(const TEXIDevices device_type, const int channel_num, + const Memcard::HeaderData& memcard_header_data) { std::unique_ptr result; @@ -116,7 +117,7 @@ std::unique_ptr EXIDevice_Create(const TEXIDevices device_type, cons case EXIDEVICE_MEMORYCARDFOLDER: { bool gci_folder = (device_type == EXIDEVICE_MEMORYCARDFOLDER); - result = std::make_unique(channel_num, gci_folder); + result = std::make_unique(channel_num, gci_folder, memcard_header_data); break; } case EXIDEVICE_MASKROM: diff --git a/Source/Core/Core/HW/EXI/EXI_Device.h b/Source/Core/Core/HW/EXI/EXI_Device.h index 0eb30904ca..9312643945 100644 --- a/Source/Core/Core/HW/EXI/EXI_Device.h +++ b/Source/Core/Core/HW/EXI/EXI_Device.h @@ -9,6 +9,11 @@ class PointerWrap; +namespace Memcard +{ +struct HeaderData; +} + namespace ExpansionInterface { enum TEXIDevices : int @@ -65,5 +70,6 @@ private: virtual void TransferByte(u8& byte); }; -std::unique_ptr EXIDevice_Create(TEXIDevices device_type, int channel_num); +std::unique_ptr EXIDevice_Create(TEXIDevices device_type, int channel_num, + const Memcard::HeaderData& memcard_header_data); } // namespace ExpansionInterface diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp index b39b6963d7..4f0b3830f3 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.cpp @@ -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(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(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(strDirectoryName + DIR_SEP, card_index, sizeMb, - shift_jis, CurrentGameId); + memorycard = std::make_unique(strDirectoryName + DIR_SEP, card_index, + header_data, CurrentGameId); } void CEXIMemoryCard::SetupRawMemcard(u16 sizeMb) diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.h b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.h index 4075637f25..f0c9411309 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMemoryCard.h @@ -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 @@ public: 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 callback); diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp index 9fe0c0dccb..7af9e44778 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.cpp @@ -149,13 +149,11 @@ std::vector GCMemcardDirectory::GetFileNamesForGameID(const std::st return filenames; } -GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u16 size_mbits, - bool shift_jis, int game_id) - : MemoryCardBase(slot, size_mbits), m_game_id(game_id), m_last_block(-1), - m_hdr(g_SRAM.settings_ex.flash_id[slot], size_mbits, shift_jis, g_SRAM.settings.rtc_bias, - static_cast(g_SRAM.settings.language), - Common::Timer::GetLocalTimeSinceJan1970() - ExpansionInterface::CEXIIPL::GC_EPOCH), - m_bat1(size_mbits), m_saves(0), m_save_directory(directory), m_exiting(false) +GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, + const Memcard::HeaderData& header_data, u32 game_id) + : MemoryCardBase(slot, header_data.m_size_mb), m_game_id(game_id), m_last_block(-1), + m_hdr(header_data), m_bat1(header_data.m_size_mb), m_saves(0), m_save_directory(directory), + m_exiting(false) { // Use existing header data if available { diff --git a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h index dbda7b3778..374221de23 100644 --- a/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h +++ b/Source/Core/Core/HW/GCMemcard/GCMemcardDirectory.h @@ -21,8 +21,8 @@ void MigrateFromMemcardFile(const std::string& directory_name, int card_index); class GCMemcardDirectory : public MemoryCardBase { public: - GCMemcardDirectory(const std::string& directory, int slot, u16 size_mbits, bool shift_jis, - int game_id); + GCMemcardDirectory(const std::string& directory, int slot, const Memcard::HeaderData& header_data, + u32 game_id); ~GCMemcardDirectory(); GCMemcardDirectory(const GCMemcardDirectory&) = delete; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 84de8d864b..b12a5f1462 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 115; // Last changed in PR 8722 +constexpr u32 STATE_VERSION = 116; // Last changed in PR 8879 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list, From 476c95900d00dff249422d031e783935d1098fdc Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Tue, 16 Jun 2020 19:11:00 +0200 Subject: [PATCH 5/5] 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. --- Source/Core/Core/HW/EXI/EXI.cpp | 8 +++--- Source/Core/Core/HW/EXI/EXI.h | 4 ++- Source/Core/Core/HW/EXI/EXI_Channel.cpp | 32 +++++++++++++++++++++ Source/Core/Core/HW/GCMemcard/GCMemcard.cpp | 11 +++++++ Source/Core/Core/HW/GCMemcard/GCMemcard.h | 3 ++ 5 files changed, 53 insertions(+), 5 deletions(-) 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;