@@ -64,14 +64,16 @@ GCMemcard::GCMemcard()
{
}

std::optional<GCMemcard> GCMemcard::Create(std::string filename, u16 size_mbits, bool shift_jis)
std::optional<GCMemcard> 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);
@@ -302,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()
@@ -1367,29 +1369,32 @@ std::optional<std::vector<GCMemcardAnimationFrameRGBA8>> 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,30 +1543,65 @@ 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<Header>);
std::memset(this, 0xFF, BLOCK_SIZE);
}

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
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;
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)(g_SRAM.settings_ex.flash_id[slot][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 = g_SRAM.settings.rtc_bias;
m_sram_language = static_cast<u32>(g_SRAM.settings.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;
}

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,
u32 sram_language, u64 format_time)
{
static_assert(std::is_trivially_copyable_v<Header>);
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<Header>);
std::memset(this, 0xFF, BLOCK_SIZE);
m_data = data;
FixChecksums();
}

@@ -1609,7 +1649,7 @@ std::pair<u16, u16> Header::CalculateChecksums() const
std::array<u8, sizeof(Header)> 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);
@@ -1620,7 +1660,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
@@ -166,9 +166,13 @@ struct GCMBlock
void Erase();
std::array<u8, BLOCK_SIZE> m_block;
};
static_assert(sizeof(GCMBlock) == BLOCK_SIZE);
static_assert(std::is_trivially_copyable_v<GCMBlock>);

#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)
@@ -196,6 +200,18 @@ struct Header

// 2 bytes at 0x0024: Encoding (Windows-1252 or Shift JIS)
Common::BigEndianValue<u16> m_encoding;
};
static_assert(std::is_trivially_copyable_v<HeaderData>);

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;

// 468 bytes at 0x0026: Unused (0xff)
std::array<u8, 468> m_unused_1;
@@ -213,8 +229,15 @@ struct Header
// 0x1e00 bytes at 0x0200: Unused (0xff)
std::array<u8, 7680> 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);

// 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<u32, u32> CalculateSerial() const;
@@ -225,6 +248,7 @@ struct Header
GCMemcardErrorCode CheckForErrors(u16 card_size_mbits) const;
};
static_assert(sizeof(Header) == BLOCK_SIZE);
static_assert(std::is_trivially_copyable_v<Header>);

struct DEntry
{
@@ -302,6 +326,7 @@ struct DEntry
Common::BigEndianValue<u32> m_comments_address;
};
static_assert(sizeof(DEntry) == DENTRY_SIZE);
static_assert(std::is_trivially_copyable_v<DEntry>);

struct BlockAlloc;

@@ -337,6 +362,7 @@ struct Directory
GCMemcardErrorCode CheckForErrorsWithBat(const BlockAlloc& bat) const;
};
static_assert(sizeof(Directory) == BLOCK_SIZE);
static_assert(std::is_trivially_copyable_v<Directory>);

struct BlockAlloc
{
@@ -371,6 +397,7 @@ struct BlockAlloc
GCMemcardErrorCode CheckForErrors(u16 size_mbits) const;
};
static_assert(sizeof(BlockAlloc) == BLOCK_SIZE);
static_assert(std::is_trivially_copyable_v<BlockAlloc>);
#pragma pack(pop)

class GCMemcard
@@ -401,7 +428,9 @@ class GCMemcard
void UpdateBat(const BlockAlloc& bat);

public:
static std::optional<GCMemcard> Create(std::string filename, u16 size_mbits, bool shift_jis);
static 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);

static std::pair<GCMemcardErrorCode, std::optional<GCMemcard>> Open(std::string filename);

@@ -413,9 +442,10 @@ class GCMemcard
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<GCMBlock>& FileBuffer);
static s32 PSO_MakeSaveGameValid(const Header& cardheader, const DEntry& direntry,
@@ -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";
@@ -145,11 +149,11 @@ std::vector<std::string> 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(slot, size_mbits, shift_jis), 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
{
@@ -195,7 +199,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
@@ -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;
@@ -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<u8[]>(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<u32>(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.");
@@ -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,
@@ -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<u32>(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;