@@ -5,6 +5,7 @@
#pragma once
#include < algorithm>
#include < array>
#include < string>
#include < vector>
@@ -92,42 +93,68 @@ class MemoryCardBase
struct GCMBlock
{
GCMBlock () { Erase (); }
void Erase () { memset (block , 0xFF , BLOCK_SIZE ); }
u8 block[ BLOCK_SIZE] ;
void Erase () { memset (m_block. data () , 0xFF , m_block. size () ); }
std::array<u8, BLOCK_SIZE> m_block ;
};
void calc_checksumsBE (const u16* buf, u32 length, u16* csum, u16* inv_csum);
#pragma pack(push, 1)
struct Header // Offset Size Description
struct Header
{
// Serial in libogc
u8 serial[12 ]; // 0x0000 12 ?
u64 formatTime; // 0x000c 8 Time of format (OSTime value)
u32 SramBias; // 0x0014 4 SRAM bias at time of format
u32 SramLang; // 0x0018 4 SRAM language
u8 Unk2[4 ]; // 0x001c 4 ? almost always 0
// end Serial in libogc
u8 deviceID[2 ]; // 0x0020 2 0 if formated in slot A 1 if formated in slot B
u8 SizeMb[2 ]; // 0x0022 2 Size of memcard in Mbits
u16 Encoding; // 0x0024 2 Encoding (Windows-1252 or Shift JIS)
u8 Unused1[468 ]; // 0x0026 468 Unused (0xff)
u16 UpdateCounter; // 0x01fa 2 Update Counter (?, probably unused)
u16 Checksum; // 0x01fc 2 Additive Checksum
u16 Checksum_Inv; // 0x01fe 2 Inverse Checksum
u8 Unused2[7680 ]; // 0x0200 0x1e00 Unused (0xff)
// NOTE: libogc refers to 'Serial' as the first 0x20 bytes of the header,
// so the data from m_serial until m_unknown_2 (inclusive)
// 12 bytes at 0x0000
std::array<u8, 12 > m_serial;
// 8 bytes at 0x000c: Time of format (OSTime value)
Common::BigEndianValue<u64> m_format_time;
// 4 bytes at 0x0014; SRAM bias at time of format
u32 m_sram_bias;
// 4 bytes at 0x0018: SRAM language
Common::BigEndianValue<u32> m_sram_language;
// 4 bytes at 0x001c: ? almost always 0
std::array<u8, 4 > m_unknown_2;
// 2 bytes at 0x0020: 0 if formated in slot A, 1 if formated in slot B
Common::BigEndianValue<u16> m_device_id;
// 2 bytes at 0x0022: Size of memcard in Mbits
Common::BigEndianValue<u16> m_size_mb;
// 2 bytes at 0x0024: Encoding (Windows-1252 or Shift JIS)
Common::BigEndianValue<u16> m_encoding;
// 468 bytes at 0x0026: Unused (0xff)
std::array<u8, 468 > m_unused_1;
// 2 bytes at 0x01fa: Update Counter (?, probably unused)
u16 m_update_counter;
// 2 bytes at 0x01fc: Additive Checksum
u16 m_checksum;
// 2 bytes at 0x01fe: Inverse Checksum
u16 m_checksum_inv;
// 0x1e00 bytes at 0x0200: Unused (0xff)
std::array<u8, 7680 > m_unused_2;
void CARD_GetSerialNo (u32* serial1, u32* serial2) const
{
u32 _serial [8 ];
u32 serial [8 ];
for (int i = 0 ; i < 8 ; i++)
{
memcpy (&_serial [i], (u8*)this + (i * 4 ), 4 );
memcpy (&serial [i], (u8*)this + (i * 4 ), 4 );
}
*serial1 = _serial [0 ] ^ _serial [2 ] ^ _serial [4 ] ^ _serial [6 ];
*serial2 = _serial [1 ] ^ _serial [3 ] ^ _serial [5 ] ^ _serial [7 ];
*serial1 = serial [0 ] ^ serial [2 ] ^ serial [4 ] ^ serial [6 ];
*serial2 = serial [1 ] ^ serial [3 ] ^ serial [5 ] ^ serial [7 ];
}
// Nintendo format algorithm.
@@ -136,41 +163,53 @@ struct Header // Offset Size Description
explicit Header (int slot = 0 , u16 sizeMb = MemCard2043Mb, bool shift_jis = false )
{
memset (this , 0xFF , BLOCK_SIZE);
*(u16*)SizeMb = BE16 ( sizeMb) ;
Encoding = BE16 ( shift_jis ? 1 : 0 ) ;
m_size_mb = sizeMb;
m_encoding = shift_jis ? 1 : 0 ;
u64 rand = Common::Timer::GetLocalTimeSinceJan1970 () - ExpansionInterface::CEXIIPL::GC_EPOCH;
formatTime = Common::swap64 ( rand ) ;
m_format_time = rand ;
for (int i = 0 ; i < 12 ; i++)
{
rand = (((rand * (u64)0x0000000041c64e6dULL ) + (u64)0x0000000000003039ULL ) >> 16 );
serial [i] = (u8)(g_SRAM.settings_ex .flash_id [slot][i] + (u32)rand );
m_serial [i] = (u8)(g_SRAM.settings_ex .flash_id [slot][i] + (u32)rand );
rand = (((rand * (u64)0x0000000041c64e6dULL ) + (u64)0x0000000000003039ULL ) >> 16 );
rand &= (u64)0x0000000000007fffULL ;
}
SramBias = g_SRAM.settings .rtc_bias ;
SramLang = BE32 (g_SRAM.settings .language );
// TODO: determine the purpose of Unk2 1 works for slot A, 0 works for both slot A and slot B
*(u32*)&Unk2 = 0 ; // = _viReg[55]; static vu16* const _viReg = (u16*)0xCC002000;
*(u16*)&deviceID = 0 ;
calc_checksumsBE ((u16*)this , 0xFE , &Checksum, &Checksum_Inv);
m_sram_bias = g_SRAM.settings .rtc_bias ;
m_sram_language = static_cast <u32>(g_SRAM.settings .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 ;
calc_checksumsBE ((u16*)this , 0xFE , &m_checksum, &m_checksum_inv);
}
};
static_assert (sizeof (Header) == BLOCK_SIZE);
struct DEntry
{
static const u8 DENTRY_SIZE = 0x40 ;
DEntry () { memset (this , 0xFF , DENTRY_SIZE); }
std::string GCI_FileName () const
{
std::string filename = std::string ((char *)Makercode, 2 ) + ' -' +
std::string ((char *)Gamecode, 4 ) + ' -' + (char *)Filename + " .gci" ;
std::string filename =
std::string (reinterpret_cast <const char *>(m_makercode.data ()), m_makercode.size ()) + ' -' +
std::string (reinterpret_cast <const char *>(m_gamecode.data ()), m_gamecode.size ()) + ' -' +
reinterpret_cast <const char *>(m_filename.data ()) + " .gci" ;
return Common::EscapeFileName (filename);
}
u8 Gamecode[4 ]; // 0x00 0x04 Gamecode
u8 Makercode[2 ]; // 0x04 0x02 Makercode
u8 Unused1; // 0x06 0x01 reserved/unused (always 0xff, has no effect)
u8 BIFlags; // 0x07 0x01 banner gfx format and icon animation (Image Key)
static constexpr std::array<u8, 4 > UNINITIALIZED_GAMECODE{{0xFF , 0xFF , 0xFF , 0xFF }};
// 4 bytes at 0x00: Gamecode
std::array<u8, 4 > m_gamecode;
// 2 bytes at 0x04: Makercode
std::array<u8, 2 > m_makercode;
// 1 byte at 0x06: reserved/unused (always 0xff, has no effect)
u8 m_unused_1;
// 1 byte at 0x07: banner gfx format and icon animation (Image Key)
// Bit(s) Description
// 2 Icon Animation 0: forward 1: ping-pong
// 1 [--0: No Banner 1: Banner present--] WRONG! YAGCD LIES!
@@ -180,98 +219,133 @@ struct DEntry
// 01 CI8 banner
// 10 RGB5A3 banner
// 11 ? maybe ==00? Time Splitters 2 and 3 have it and don't have banner
//
u8 Filename[DENTRY_STRLEN]; // 0x08 0x20 Filename
u8 ModTime[4 ]; // 0x28 0x04 Time of file's last modification in seconds since 12am,
// January 1st, 2000
u8 ImageOffset[4 ]; // 0x2c 0x04 image data offset
u8 IconFmt[2 ]; // 0x30 0x02 icon gfx format (2bits per icon)
u8 m_banner_and_icon_flags;
// 0x20 bytes at 0x08: Filename
std::array<u8, DENTRY_STRLEN> m_filename;
// 4 bytes at 0x28: Time of file's last modification in seconds since 12am, January 1st, 2000
Common::BigEndianValue<u32> m_modification_time;
// 4 bytes at 0x2c: image data offset
Common::BigEndianValue<u32> m_image_offset;
// 2 bytes at 0x30: icon gfx format (2bits per icon)
// Bits Description
// 00 No icon
// 01 CI8 with a shared color palette after the last frame
// 10 RGB5A3
// 11 CI8 with a unique color palette after itself
//
u8 AnimSpeed[2 ]; // 0x32 0x02 Animation speed (2bits per icon) (*1)
Common::BigEndianValue<u16> m_icon_format;
// 2 bytes at 0x32: Animation speed (2bits per icon)
// Bits Description
// 00 No icon
// 01 Icon lasts for 4 frames
// 10 Icon lasts for 8 frames
// 11 Icon lasts for 12 frames
//
u8 Permissions; // 0x34 0x01 File-permissions
Common::BigEndianValue<u16> m_animation_speed;
// 1 byte at 0x34: File-permissions
// Bit Permission Description
// 4 no move File cannot be moved by the IPL
// 3 no copy File cannot be copied by the IPL
// 2 public Can be read by any game
//
u8 CopyCounter; // 0x35 0x01 Copy counter (*2)
u8 FirstBlock[2 ]; // 0x36 0x02 Block no of first block of file (0 == offset 0)
u8 BlockCount[2 ]; // 0x38 0x02 File-length (number of blocks in file)
u8 Unused2[2 ]; // 0x3a 0x02 Reserved/unused (always 0xffff, has no effect)
u8 CommentsAddr[4 ]; // 0x3c 0x04 Address of the two comments within the file data (*3)
u8 m_file_permissions;
// 1 byte at 0x35: Copy counter
u8 m_copy_counter;
// 2 bytes at 0x36: Block number of first block of file (0 == offset 0)
Common::BigEndianValue<u16> m_first_block;
// 2 bytes at 0x38: File-length (number of blocks in file)
Common::BigEndianValue<u16> m_block_count;
// 2 bytes at 0x3a: Reserved/unused (always 0xffff, has no effect)
std::array<u8, 2 > m_unused_2;
// 4 bytes at 0x3c: Address of the two comments within the file data
Common::BigEndianValue<u32> m_comments_address;
};
static_assert (sizeof (DEntry) == DENTRY_SIZE);
struct Directory
{
DEntry Dir[ DIRLEN] ; // 0x0000 Directory Entries (max 127)
u8 Padding[ 0x3a ] ;
u16 UpdateCounter ; // 0x1ffa 2 Update Counter
u16 Checksum; // 0x1ffc 2 Additive Checksum
u16 Checksum_Inv; // 0x1ffe 2 Inverse Checksum
std::array< DEntry, DIRLEN> m_dir_entries ; // 0x0000 Directory Entries (max 127)
std::array<u8, 0x3a > m_padding ;
Common::BigEndianValue< u16> m_update_counter ; // 0x1ffa 2 Update Counter
u16 m_checksum; // 0x1ffc 2 Additive Checksum
u16 m_checksum_inv; // 0x1ffe 2 Inverse Checksum
Directory ()
{
memset (this , 0xFF , BLOCK_SIZE);
UpdateCounter = 0 ;
Checksum = BE16 (0xF003 );
Checksum_Inv = 0 ;
m_update_counter = 0 ;
m_checksum = BE16 (0xF003 );
m_checksum_inv = 0 ;
}
void Replace (DEntry d, int idx)
{
Dir [idx] = d;
m_dir_entries [idx] = d;
fixChecksums ();
}
void fixChecksums () { calc_checksumsBE ((u16*)this , 0xFFE , &Checksum , &Checksum_Inv ); }
void fixChecksums () { calc_checksumsBE ((u16*)this , 0xFFE , &m_checksum , &m_checksum_inv ); }
};
static_assert (sizeof (Directory) == BLOCK_SIZE);
struct BlockAlloc
{
u16 Checksum; // 0x0000 2 Additive Checksum
u16 Checksum_Inv; // 0x0002 2 Inverse Checksum
u16 UpdateCounter; // 0x0004 2 Update Counter
u16 FreeBlocks; // 0x0006 2 Free Blocks
u16 LastAllocated; // 0x0008 2 Last allocated Block
u16 Map[BAT_SIZE]; // 0x000a 0x1ff8 Map of allocated Blocks
// 2 bytes at 0x0000: Additive Checksum
u16 m_checksum;
// 2 bytes at 0x0002: Inverse Checksum
u16 m_checksum_inv;
// 2 bytes at 0x0004: Update Counter
Common::BigEndianValue<u16> m_update_counter;
// 2 bytes at 0x0006: Free Blocks
Common::BigEndianValue<u16> m_free_blocks;
// 2 bytes at 0x0008: Last allocated Block
Common::BigEndianValue<u16> m_last_allocated_block;
// 0x1ff8 bytes at 0x000a: Map of allocated Blocks
std::array<Common::BigEndianValue<u16>, BAT_SIZE> m_map;
u16 GetNextBlock (u16 Block) const ;
u16 NextFreeBlock (u16 MaxBlock, u16 StartingBlock = MC_FST_BLOCKS) const ;
bool ClearBlocks (u16 StartingBlock, u16 Length);
void fixChecksums () { calc_checksumsBE ((u16*)&UpdateCounter, 0xFFE , &Checksum, &Checksum_Inv); }
void fixChecksums ()
{
calc_checksumsBE ((u16*)&m_update_counter, 0xFFE , &m_checksum, &m_checksum_inv);
}
explicit BlockAlloc (u16 sizeMb = MemCard2043Mb)
{
memset (this , 0 , BLOCK_SIZE);
// UpdateCounter = 0;
FreeBlocks = BE16 ((sizeMb * MBIT_TO_BLOCKS) - MC_FST_BLOCKS);
LastAllocated = BE16 (4 );
m_free_blocks = (sizeMb * MBIT_TO_BLOCKS) - MC_FST_BLOCKS;
m_last_allocated_block = 4 ;
fixChecksums ();
}
u16 AssignBlocksContiguous (u16 length)
{
u16 starting = BE16 (LastAllocated) + 1 ;
if (length > BE16 (FreeBlocks) )
u16 starting = m_last_allocated_block + 1 ;
if (length > m_free_blocks )
return 0xFFFF ;
u16 current = starting;
while ((current - starting + 1 ) < length)
{
Map [current - 5 ] = BE16 ( current + 1 ) ;
m_map [current - 5 ] = current + 1 ;
current++;
}
Map [current - 5 ] = 0xFFFF ;
LastAllocated = BE16 ( current) ;
FreeBlocks = BE16 ( BE16 (FreeBlocks) - length) ;
m_map [current - 5 ] = 0xFFFF ;
m_last_allocated_block = current;
m_free_blocks = m_free_blocks - length;
fixChecksums ();
return BE16 ( starting) ;
return starting;
}
};
static_assert (sizeof (BlockAlloc) == BLOCK_SIZE);
#pragma pack(pop)
class GCIFile
@@ -280,9 +354,11 @@ class GCIFile
bool LoadSaveBlocks ();
bool HasCopyProtection () const
{
if ((strcmp ((char *)m_gci_header.Filename , " PSO_SYSTEM" ) == 0 ) ||
(strcmp ((char *)m_gci_header.Filename , " PSO3_SYSTEM" ) == 0 ) ||
(strcmp ((char *)m_gci_header.Filename , " f_zero.dat" ) == 0 ))
if ((strcmp (reinterpret_cast <const char *>(m_gci_header.m_filename .data ()), " PSO_SYSTEM" ) ==
0 ) ||
(strcmp (reinterpret_cast <const char *>(m_gci_header.m_filename .data ()), " PSO3_SYSTEM" ) ==
0 ) ||
(strcmp (reinterpret_cast <const char *>(m_gci_header.m_filename .data ()), " f_zero.dat" ) == 0 ))
return true ;
return false ;
}
@@ -300,20 +376,28 @@ class GCMemcard
{
private:
bool m_valid;
std::string m_fileName ;
std::string m_filename ;
u32 maxBlock ;
u16 m_sizeMb ;
u32 m_size_blocks ;
u16 m_size_mb ;
Header hdr;
Directory dir, dir_backup, *CurrentDir, *PreviousDir;
BlockAlloc bat, bat_backup, *CurrentBat, *PreviousBat;
Header m_header_block;
std::array<Directory, 2 > m_directory_blocks;
std::array<BlockAlloc, 2 > m_bat_blocks;
std::vector<GCMBlock> m_data_blocks;
std::vector<GCMBlock> mc_data_blocks;
int m_active_directory;
int m_active_bat;
u32 ImportGciInternal (File::IOFile&& gci, const std::string& inputFile,
const std::string& outputFile);
void InitDirBatPointers ();
void InitActiveDirBat ();
const Directory& GetActiveDirectory () const ;
const BlockAlloc& GetActiveBat () const ;
void UpdateDirectory (const Directory& directory);
void UpdateBat (const BlockAlloc& bat);
public:
explicit GCMemcard (const std::string& fileName, bool forceCreation = false ,
@@ -366,8 +450,9 @@ class GCMemcard
u32 DEntry_CommentsAddress (u8 index) const ;
std::string GetSaveComment1 (u8 index) const ;
std::string GetSaveComment2 (u8 index) const ;
// Copies a DEntry from u8 index to DEntry& data
bool GetDEntry (u8 index, DEntry& dest) const ;
// Fetches a DEntry from the given file index.
std::optional<DEntry> GetDEntry (u8 index) const ;
u32 GetSaveData (u8 index, std::vector<GCMBlock>& saveBlocks) const ;