Merge pull request #8019 from AdmiralCurtiss/gcmemcard-header-cleanup

GCMemcard: A little cleanup.
This commit is contained in:
Léo Lam 2019-05-25 19:20:43 +02:00 committed by GitHub
commit edf988b465
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 408 additions and 293 deletions

View File

@ -5,6 +5,7 @@
#include "Core/HW/GCMemcard/GCMemcard.h" #include "Core/HW/GCMemcard/GCMemcard.h"
#include <algorithm> #include <algorithm>
#include <cassert>
#include <cinttypes> #include <cinttypes>
#include <cstring> #include <cstring>
#include <vector> #include <vector>
@ -270,54 +271,50 @@ bool GCMemcard::Save()
return mcdFile.Close(); return mcdFile.Close();
} }
void calc_checksumsBE(const u16* buf, u32 length, u16* csum, u16* inv_csum) std::pair<u16, u16> CalculateMemcardChecksums(const u8* data, size_t size)
{ {
*csum = *inv_csum = 0; assert(size % 2 == 0);
u16 csum = 0;
u16 inv_csum = 0;
for (u32 i = 0; i < length; ++i) for (size_t i = 0; i < size; i += 2)
{ {
// weird warnings here u16 d = Common::swap16(&data[i]);
*csum += BE16(buf[i]); csum += d;
*inv_csum += BE16((u16)(buf[i] ^ 0xffff)); inv_csum += static_cast<u16>(d ^ 0xffff);
}
*csum = BE16(*csum);
*inv_csum = BE16(*inv_csum);
if (*csum == 0xffff)
{
*csum = 0;
}
if (*inv_csum == 0xffff)
{
*inv_csum = 0;
} }
csum = Common::swap16(csum);
inv_csum = Common::swap16(inv_csum);
if (csum == 0xffff)
csum = 0;
if (inv_csum == 0xffff)
inv_csum = 0;
return std::make_pair(csum, inv_csum);
} }
u32 GCMemcard::TestChecksums() const u32 GCMemcard::TestChecksums() const
{ {
u16 csum = 0, csum_inv = 0; const auto [csum_hdr, cinv_hdr] = m_header_block.CalculateChecksums();
const auto [csum_dir0, cinv_dir0] = m_directory_blocks[0].CalculateChecksums();
const auto [csum_dir1, cinv_dir1] = m_directory_blocks[1].CalculateChecksums();
const auto [csum_bat0, cinv_bat0] = m_bat_blocks[0].CalculateChecksums();
const auto [csum_bat1, cinv_bat1] = m_bat_blocks[1].CalculateChecksums();
u32 results = 0; u32 results = 0;
if ((m_header_block.m_checksum != csum_hdr) || (m_header_block.m_checksum_inv != cinv_hdr))
calc_checksumsBE((u16*)&m_header_block, 0xFE, &csum, &csum_inv);
if ((m_header_block.m_checksum != csum) || (m_header_block.m_checksum_inv != csum_inv))
results |= 1; results |= 1;
if ((m_directory_blocks[0].m_checksum != csum_dir0) ||
calc_checksumsBE((u16*)&m_directory_blocks[0], 0xFFE, &csum, &csum_inv); (m_directory_blocks[0].m_checksum_inv != cinv_dir0))
if ((m_directory_blocks[0].m_checksum != csum) ||
(m_directory_blocks[0].m_checksum_inv != csum_inv))
results |= 2; results |= 2;
if ((m_directory_blocks[1].m_checksum != csum_dir1) ||
calc_checksumsBE((u16*)&m_directory_blocks[1], 0xFFE, &csum, &csum_inv); (m_directory_blocks[1].m_checksum_inv != cinv_dir1))
if ((m_directory_blocks[1].m_checksum != csum) ||
(m_directory_blocks[1].m_checksum_inv != csum_inv))
results |= 4; results |= 4;
if ((m_bat_blocks[0].m_checksum != csum_bat0) || (m_bat_blocks[0].m_checksum_inv != cinv_bat0))
calc_checksumsBE((u16*)(((u8*)&m_bat_blocks[0]) + 4), 0xFFE, &csum, &csum_inv);
if ((m_bat_blocks[0].m_checksum != csum) || (m_bat_blocks[0].m_checksum_inv != csum_inv))
results |= 8; results |= 8;
if ((m_bat_blocks[1].m_checksum != csum_bat1) || (m_bat_blocks[1].m_checksum_inv != cinv_bat1))
calc_checksumsBE((u16*)(((u8*)&m_bat_blocks[1]) + 4), 0xFFE, &csum, &csum_inv);
if ((m_bat_blocks[1].m_checksum != csum) || (m_bat_blocks[1].m_checksum_inv != csum_inv))
results |= 16; results |= 16;
return results; return results;
@ -328,16 +325,11 @@ bool GCMemcard::FixChecksums()
if (!m_valid) if (!m_valid)
return false; return false;
calc_checksumsBE((u16*)&m_header_block, 0xFE, &m_header_block.m_checksum, m_header_block.FixChecksums();
&m_header_block.m_checksum_inv); m_directory_blocks[0].FixChecksums();
calc_checksumsBE((u16*)&m_directory_blocks[0], 0xFFE, &m_directory_blocks[0].m_checksum, m_directory_blocks[1].FixChecksums();
&m_directory_blocks[0].m_checksum_inv); m_bat_blocks[0].FixChecksums();
calc_checksumsBE((u16*)&m_directory_blocks[1], 0xFFE, &m_directory_blocks[1].m_checksum, m_bat_blocks[1].FixChecksums();
&m_directory_blocks[1].m_checksum_inv);
calc_checksumsBE((u16*)&m_bat_blocks[0] + 2, 0xFFE, &m_bat_blocks[0].m_checksum,
&m_bat_blocks[0].m_checksum_inv);
calc_checksumsBE((u16*)&m_bat_blocks[1] + 2, 0xFFE, &m_bat_blocks[1].m_checksum,
&m_bat_blocks[1].m_checksum_inv);
return true; return true;
} }
@ -594,61 +586,106 @@ std::optional<DEntry> GCMemcard::GetDEntry(u8 index) const
return GetActiveDirectory().m_dir_entries[index]; return GetActiveDirectory().m_dir_entries[index];
} }
u16 BlockAlloc::GetNextBlock(u16 Block) const BlockAlloc::BlockAlloc(u16 size_mbits)
{ {
if ((Block < MC_FST_BLOCKS) || (Block > 4091)) memset(this, 0, BLOCK_SIZE);
m_free_blocks = (size_mbits * MBIT_TO_BLOCKS) - MC_FST_BLOCKS;
m_last_allocated_block = 4;
FixChecksums();
}
u16 BlockAlloc::GetNextBlock(u16 block) const
{
// FIXME: This is fishy, shouldn't that be in range [5, 4096[?
if ((block < MC_FST_BLOCKS) || (block > 4091))
return 0; return 0;
return m_map[Block - MC_FST_BLOCKS]; return m_map[block - MC_FST_BLOCKS];
} }
// Parameters and return value are expected as memory card block index, // Parameters and return value are expected as memory card block index,
// not BAT index; that is, block 5 is the first file data block. // not BAT index; that is, block 5 is the first file data block.
u16 BlockAlloc::NextFreeBlock(u16 MaxBlock, u16 StartingBlock) const u16 BlockAlloc::NextFreeBlock(u16 max_block, u16 starting_block) const
{ {
if (m_free_blocks > 0) if (m_free_blocks > 0)
{ {
StartingBlock = std::clamp<u16>(StartingBlock, MC_FST_BLOCKS, BAT_SIZE + MC_FST_BLOCKS); starting_block = std::clamp<u16>(starting_block, MC_FST_BLOCKS, BAT_SIZE + MC_FST_BLOCKS);
MaxBlock = std::clamp<u16>(MaxBlock, MC_FST_BLOCKS, BAT_SIZE + MC_FST_BLOCKS); max_block = std::clamp<u16>(max_block, MC_FST_BLOCKS, BAT_SIZE + MC_FST_BLOCKS);
for (u16 i = StartingBlock; i < MaxBlock; ++i) for (u16 i = starting_block; i < max_block; ++i)
if (m_map[i - MC_FST_BLOCKS] == 0) if (m_map[i - MC_FST_BLOCKS] == 0)
return i; return i;
for (u16 i = MC_FST_BLOCKS; i < StartingBlock; ++i) for (u16 i = MC_FST_BLOCKS; i < starting_block; ++i)
if (m_map[i - MC_FST_BLOCKS] == 0) if (m_map[i - MC_FST_BLOCKS] == 0)
return i; return i;
} }
return 0xFFFF; return 0xFFFF;
} }
bool BlockAlloc::ClearBlocks(u16 FirstBlock, u16 BlockCount) bool BlockAlloc::ClearBlocks(u16 starting_block, u16 block_count)
{ {
std::vector<u16> blocks; std::vector<u16> blocks;
while (FirstBlock != 0xFFFF && FirstBlock != 0) while (starting_block != 0xFFFF && starting_block != 0)
{ {
blocks.push_back(FirstBlock); blocks.push_back(starting_block);
FirstBlock = GetNextBlock(FirstBlock); starting_block = GetNextBlock(starting_block);
} }
if (FirstBlock > 0) if (starting_block > 0)
{ {
size_t length = blocks.size(); size_t length = blocks.size();
if (length != BlockCount) if (length != block_count)
{ {
return false; return false;
} }
for (unsigned int i = 0; i < length; ++i) for (unsigned int i = 0; i < length; ++i)
m_map[blocks.at(i) - MC_FST_BLOCKS] = 0; m_map[blocks.at(i) - MC_FST_BLOCKS] = 0;
m_free_blocks = m_free_blocks + BlockCount; m_free_blocks = m_free_blocks + block_count;
return true; return true;
} }
return false; return false;
} }
u32 GCMemcard::GetSaveData(u8 index, std::vector<GCMBlock>& Blocks) const void BlockAlloc::FixChecksums()
{
std::tie(m_checksum, m_checksum_inv) = CalculateChecksums();
}
u16 BlockAlloc::AssignBlocksContiguous(u16 length)
{
u16 starting = m_last_allocated_block + 1;
if (length > m_free_blocks)
return 0xFFFF;
u16 current = starting;
while ((current - starting + 1) < length)
{
m_map[current - 5] = current + 1;
current++;
}
m_map[current - 5] = 0xFFFF;
m_last_allocated_block = current;
m_free_blocks = m_free_blocks - length;
FixChecksums();
return starting;
}
std::pair<u16, u16> BlockAlloc::CalculateChecksums() const
{
static_assert(std::is_trivially_copyable<BlockAlloc>());
std::array<u8, sizeof(BlockAlloc)> raw;
memcpy(raw.data(), this, raw.size());
constexpr size_t checksum_area_start = offsetof(BlockAlloc, m_update_counter);
constexpr size_t checksum_area_end = sizeof(BlockAlloc);
constexpr size_t checksum_area_size = checksum_area_end - checksum_area_start;
return CalculateMemcardChecksums(&raw[checksum_area_start], checksum_area_size);
}
GCMemcardGetSaveDataRetVal GCMemcard::GetSaveData(u8 index, std::vector<GCMBlock>& Blocks) const
{ {
if (!m_valid) if (!m_valid)
return NOMEMCARD; return GCMemcardGetSaveDataRetVal::NOMEMCARD;
u16 block = DEntry_FirstBlock(index); u16 block = DEntry_FirstBlock(index);
u16 BlockCount = DEntry_BlockCount(index); u16 BlockCount = DEntry_BlockCount(index);
@ -656,44 +693,45 @@ u32 GCMemcard::GetSaveData(u8 index, std::vector<GCMBlock>& Blocks) const
if ((block == 0xFFFF) || (BlockCount == 0xFFFF)) if ((block == 0xFFFF) || (BlockCount == 0xFFFF))
{ {
return FAIL; return GCMemcardGetSaveDataRetVal::FAIL;
} }
u16 nextBlock = block; u16 nextBlock = block;
for (int i = 0; i < BlockCount; ++i) for (int i = 0; i < BlockCount; ++i)
{ {
if ((!nextBlock) || (nextBlock == 0xFFFF)) if ((!nextBlock) || (nextBlock == 0xFFFF))
return FAIL; return GCMemcardGetSaveDataRetVal::FAIL;
Blocks.push_back(m_data_blocks[nextBlock - MC_FST_BLOCKS]); Blocks.push_back(m_data_blocks[nextBlock - MC_FST_BLOCKS]);
nextBlock = GetActiveBat().GetNextBlock(nextBlock); nextBlock = GetActiveBat().GetNextBlock(nextBlock);
} }
return SUCCESS; return GCMemcardGetSaveDataRetVal::SUCCESS;
} }
// End DEntry functions // End DEntry functions
u32 GCMemcard::ImportFile(const DEntry& direntry, std::vector<GCMBlock>& saveBlocks) GCMemcardImportFileRetVal GCMemcard::ImportFile(const DEntry& direntry,
std::vector<GCMBlock>& saveBlocks)
{ {
if (!m_valid) if (!m_valid)
return NOMEMCARD; return GCMemcardImportFileRetVal::NOMEMCARD;
if (GetNumFiles() >= DIRLEN) if (GetNumFiles() >= DIRLEN)
{ {
return OUTOFDIRENTRIES; return GCMemcardImportFileRetVal::OUTOFDIRENTRIES;
} }
if (GetActiveBat().m_free_blocks < direntry.m_block_count) if (GetActiveBat().m_free_blocks < direntry.m_block_count)
{ {
return OUTOFBLOCKS; return GCMemcardImportFileRetVal::OUTOFBLOCKS;
} }
if (TitlePresent(direntry) != DIRLEN) if (TitlePresent(direntry) != DIRLEN)
{ {
return TITLEPRESENT; return GCMemcardImportFileRetVal::TITLEPRESENT;
} }
// find first free data block // find first free data block
u16 firstBlock = u16 firstBlock =
GetActiveBat().NextFreeBlock(m_size_blocks, GetActiveBat().m_last_allocated_block); GetActiveBat().NextFreeBlock(m_size_blocks, GetActiveBat().m_last_allocated_block);
if (firstBlock == 0xFFFF) if (firstBlock == 0xFFFF)
return OUTOFBLOCKS; return GCMemcardImportFileRetVal::OUTOFBLOCKS;
Directory UpdatedDir = GetActiveDirectory(); Directory UpdatedDir = GetActiveDirectory();
// find first free dir entry // find first free dir entry
@ -738,22 +776,22 @@ u32 GCMemcard::ImportFile(const DEntry& direntry, std::vector<GCMBlock>& saveBlo
FixChecksums(); FixChecksums();
return SUCCESS; return GCMemcardImportFileRetVal::SUCCESS;
} }
u32 GCMemcard::RemoveFile(u8 index) // index in the directory array GCMemcardRemoveFileRetVal GCMemcard::RemoveFile(u8 index) // index in the directory array
{ {
if (!m_valid) if (!m_valid)
return NOMEMCARD; return GCMemcardRemoveFileRetVal::NOMEMCARD;
if (index >= DIRLEN) if (index >= DIRLEN)
return DELETE_FAIL; return GCMemcardRemoveFileRetVal::DELETE_FAIL;
u16 startingblock = GetActiveDirectory().m_dir_entries[index].m_first_block; u16 startingblock = GetActiveDirectory().m_dir_entries[index].m_first_block;
u16 numberofblocks = GetActiveDirectory().m_dir_entries[index].m_block_count; u16 numberofblocks = GetActiveDirectory().m_dir_entries[index].m_block_count;
BlockAlloc UpdatedBat = GetActiveBat(); BlockAlloc UpdatedBat = GetActiveBat();
if (!UpdatedBat.ClearBlocks(startingblock, numberofblocks)) if (!UpdatedBat.ClearBlocks(startingblock, numberofblocks))
return DELETE_FAIL; return GCMemcardRemoveFileRetVal::DELETE_FAIL;
UpdatedBat.m_update_counter = UpdatedBat.m_update_counter + 1; UpdatedBat.m_update_counter = UpdatedBat.m_update_counter + 1;
UpdateBat(UpdatedBat); UpdateBat(UpdatedBat);
@ -769,50 +807,50 @@ u32 GCMemcard::RemoveFile(u8 index) // index in the directory array
FixChecksums(); FixChecksums();
return SUCCESS; return GCMemcardRemoveFileRetVal::SUCCESS;
} }
u32 GCMemcard::CopyFrom(const GCMemcard& source, u8 index) GCMemcardImportFileRetVal GCMemcard::CopyFrom(const GCMemcard& source, u8 index)
{ {
if (!m_valid || !source.m_valid) if (!m_valid || !source.m_valid)
return NOMEMCARD; return GCMemcardImportFileRetVal::NOMEMCARD;
std::optional<DEntry> tempDEntry = source.GetDEntry(index); std::optional<DEntry> tempDEntry = source.GetDEntry(index);
if (!tempDEntry) if (!tempDEntry)
return NOMEMCARD; return GCMemcardImportFileRetVal::NOMEMCARD;
u32 size = source.DEntry_BlockCount(index); u32 size = source.DEntry_BlockCount(index);
if (size == 0xFFFF) if (size == 0xFFFF)
return INVALIDFILESIZE; return GCMemcardImportFileRetVal::INVALIDFILESIZE;
std::vector<GCMBlock> saveData; std::vector<GCMBlock> saveData;
saveData.reserve(size); saveData.reserve(size);
switch (source.GetSaveData(index, saveData)) switch (source.GetSaveData(index, saveData))
{ {
case FAIL: case GCMemcardGetSaveDataRetVal::FAIL:
return FAIL; return GCMemcardImportFileRetVal::FAIL;
case NOMEMCARD: case GCMemcardGetSaveDataRetVal::NOMEMCARD:
return NOMEMCARD; return GCMemcardImportFileRetVal::NOMEMCARD;
default: default:
FixChecksums(); FixChecksums();
return ImportFile(*tempDEntry, saveData); return ImportFile(*tempDEntry, saveData);
} }
} }
u32 GCMemcard::ImportGci(const std::string& inputFile, const std::string& outputFile) GCMemcardImportFileRetVal GCMemcard::ImportGci(const std::string& inputFile)
{ {
if (outputFile.empty() && !m_valid) if (!m_valid)
return OPENFAIL; return GCMemcardImportFileRetVal::OPENFAIL;
File::IOFile gci(inputFile, "rb"); File::IOFile gci(inputFile, "rb");
if (!gci) if (!gci)
return OPENFAIL; return GCMemcardImportFileRetVal::OPENFAIL;
return ImportGciInternal(std::move(gci), inputFile, outputFile); return ImportGciInternal(std::move(gci), inputFile);
} }
u32 GCMemcard::ImportGciInternal(File::IOFile&& gci, const std::string& inputFile, GCMemcardImportFileRetVal GCMemcard::ImportGciInternal(File::IOFile&& gci,
const std::string& outputFile) const std::string& inputFile)
{ {
unsigned int offset; unsigned int offset;
std::string fileType; std::string fileType;
@ -829,33 +867,33 @@ u32 GCMemcard::ImportGciInternal(File::IOFile&& gci, const std::string& inputFil
if (!memcmp(tmp, "GCSAVE", 6)) // Header must be uppercase if (!memcmp(tmp, "GCSAVE", 6)) // Header must be uppercase
offset = GCS; offset = GCS;
else else
return GCSFAIL; return GCMemcardImportFileRetVal::GCSFAIL;
} }
else if (!strcasecmp(fileType.c_str(), ".sav")) else if (!strcasecmp(fileType.c_str(), ".sav"))
{ {
if (!memcmp(tmp, "DATELGC_SAVE", 0xC)) // Header must be uppercase if (!memcmp(tmp, "DATELGC_SAVE", 0xC)) // Header must be uppercase
offset = SAV; offset = SAV;
else else
return SAVFAIL; return GCMemcardImportFileRetVal::SAVFAIL;
} }
else else
return OPENFAIL; return GCMemcardImportFileRetVal::OPENFAIL;
} }
gci.Seek(offset, SEEK_SET); gci.Seek(offset, SEEK_SET);
DEntry tempDEntry; DEntry tempDEntry;
gci.ReadBytes(&tempDEntry, DENTRY_SIZE); gci.ReadBytes(&tempDEntry, DENTRY_SIZE);
const int fStart = (int)gci.Tell(); const u64 fStart = gci.Tell();
gci.Seek(0, SEEK_END); gci.Seek(0, SEEK_END);
const int length = (int)gci.Tell() - fStart; const u64 length = gci.Tell() - fStart;
gci.Seek(offset + DENTRY_SIZE, SEEK_SET); gci.Seek(offset + DENTRY_SIZE, SEEK_SET);
Gcs_SavConvert(tempDEntry, offset, length); Gcs_SavConvert(tempDEntry, offset, length);
if (length != tempDEntry.m_block_count * BLOCK_SIZE) if (length != tempDEntry.m_block_count * BLOCK_SIZE)
return LENGTHFAIL; return GCMemcardImportFileRetVal::LENGTHFAIL;
if (gci.Tell() != offset + DENTRY_SIZE) // Verify correct file position if (gci.Tell() != offset + DENTRY_SIZE) // Verify correct file position
return OPENFAIL; return GCMemcardImportFileRetVal::OPENFAIL;
u32 size = tempDEntry.m_block_count; u32 size = tempDEntry.m_block_count;
std::vector<GCMBlock> saveData; std::vector<GCMBlock> saveData;
@ -867,40 +905,11 @@ u32 GCMemcard::ImportGciInternal(File::IOFile&& gci, const std::string& inputFil
gci.ReadBytes(b.m_block.data(), b.m_block.size()); gci.ReadBytes(b.m_block.data(), b.m_block.size());
saveData.push_back(b); saveData.push_back(b);
} }
u32 ret; return ImportFile(tempDEntry, saveData);
if (!outputFile.empty())
{
File::IOFile gci2(outputFile, "wb");
bool completeWrite = true;
if (!gci2)
{
return OPENFAIL;
}
gci2.Seek(0, SEEK_SET);
if (!gci2.WriteBytes(&tempDEntry, DENTRY_SIZE))
completeWrite = false;
int fileBlocks = tempDEntry.m_block_count;
gci2.Seek(DENTRY_SIZE, SEEK_SET);
for (int i = 0; i < fileBlocks; ++i)
{
if (!gci2.WriteBytes(saveData[i].m_block.data(), saveData[i].m_block.size()))
completeWrite = false;
}
if (completeWrite)
ret = GCS;
else
ret = WRITEFAIL;
}
else
ret = ImportFile(tempDEntry, saveData);
return ret;
} }
u32 GCMemcard::ExportGci(u8 index, const std::string& fileName, const std::string& directory) const GCMemcardExportFileRetVal GCMemcard::ExportGci(u8 index, const std::string& fileName,
const std::string& directory) const
{ {
File::IOFile gci; File::IOFile gci;
int offset = GCI; int offset = GCI;
@ -910,7 +919,7 @@ u32 GCMemcard::ExportGci(u8 index, const std::string& fileName, const std::strin
std::string gciFilename; std::string gciFilename;
// GCI_FileName should only fail if the gamecode is 0xFFFFFFFF // GCI_FileName should only fail if the gamecode is 0xFFFFFFFF
if (!GCI_FileName(index, gciFilename)) if (!GCI_FileName(index, gciFilename))
return SUCCESS; return GCMemcardExportFileRetVal::SUCCESS;
gci.Open(directory + DIR_SEP + gciFilename, "wb"); gci.Open(directory + DIR_SEP + gciFilename, "wb");
} }
else else
@ -929,7 +938,7 @@ u32 GCMemcard::ExportGci(u8 index, const std::string& fileName, const std::strin
} }
if (!gci) if (!gci)
return OPENFAIL; return GCMemcardExportFileRetVal::OPENFAIL;
gci.Seek(0, SEEK_SET); gci.Seek(0, SEEK_SET);
@ -952,7 +961,7 @@ u32 GCMemcard::ExportGci(u8 index, const std::string& fileName, const std::strin
std::optional<DEntry> tempDEntry = GetDEntry(index); std::optional<DEntry> tempDEntry = GetDEntry(index);
if (!tempDEntry) if (!tempDEntry)
return NOMEMCARD; return GCMemcardExportFileRetVal::NOMEMCARD;
Gcs_SavConvert(*tempDEntry, offset); Gcs_SavConvert(*tempDEntry, offset);
gci.WriteBytes(&tempDEntry.value(), DENTRY_SIZE); gci.WriteBytes(&tempDEntry.value(), DENTRY_SIZE);
@ -960,7 +969,7 @@ u32 GCMemcard::ExportGci(u8 index, const std::string& fileName, const std::strin
u32 size = DEntry_BlockCount(index); u32 size = DEntry_BlockCount(index);
if (size == 0xFFFF) if (size == 0xFFFF)
{ {
return FAIL; return GCMemcardExportFileRetVal::FAIL;
} }
std::vector<GCMBlock> saveData; std::vector<GCMBlock> saveData;
@ -968,10 +977,10 @@ u32 GCMemcard::ExportGci(u8 index, const std::string& fileName, const std::strin
switch (GetSaveData(index, saveData)) switch (GetSaveData(index, saveData))
{ {
case FAIL: case GCMemcardGetSaveDataRetVal::FAIL:
return FAIL; return GCMemcardExportFileRetVal::FAIL;
case NOMEMCARD: case GCMemcardGetSaveDataRetVal::NOMEMCARD:
return NOMEMCARD; return GCMemcardExportFileRetVal::NOMEMCARD;
} }
gci.Seek(DENTRY_SIZE + offset, SEEK_SET); gci.Seek(DENTRY_SIZE + offset, SEEK_SET);
for (unsigned int i = 0; i < size; ++i) for (unsigned int i = 0; i < size; ++i)
@ -980,12 +989,12 @@ u32 GCMemcard::ExportGci(u8 index, const std::string& fileName, const std::strin
} }
if (gci.IsGood()) if (gci.IsGood())
return SUCCESS; return GCMemcardExportFileRetVal::SUCCESS;
else else
return WRITEFAIL; return GCMemcardExportFileRetVal::WRITEFAIL;
} }
void GCMemcard::Gcs_SavConvert(DEntry& tempDEntry, int saveType, int length) void GCMemcard::Gcs_SavConvert(DEntry& tempDEntry, int saveType, u64 length)
{ {
switch (saveType) switch (saveType)
{ {
@ -1238,12 +1247,6 @@ bool GCMemcard::Format(u8* card_data, bool shift_jis, u16 SizeMb)
bool GCMemcard::Format(bool shift_jis, u16 SizeMb) bool GCMemcard::Format(bool shift_jis, u16 SizeMb)
{ {
memset(&m_header_block, 0xFF, BLOCK_SIZE);
memset(&m_directory_blocks[0], 0xFF, BLOCK_SIZE);
memset(&m_directory_blocks[1], 0xFF, BLOCK_SIZE);
memset(&m_bat_blocks[0], 0, BLOCK_SIZE);
memset(&m_bat_blocks[1], 0, BLOCK_SIZE);
m_header_block = Header(SLOT_A, SizeMb, shift_jis); m_header_block = Header(SLOT_A, SizeMb, shift_jis);
m_directory_blocks[0] = m_directory_blocks[1] = Directory(); 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(SizeMb);
@ -1274,7 +1277,6 @@ s32 GCMemcard::FZEROGX_MakeSaveGameValid(const Header& cardheader, const DEntry&
std::vector<GCMBlock>& FileBuffer) std::vector<GCMBlock>& FileBuffer)
{ {
u32 i, j; u32 i, j;
u32 serial1, serial2;
u16 chksum = 0xFFFF; u16 chksum = 0xFFFF;
// check for F-Zero GX system file // check for F-Zero GX system file
@ -1286,7 +1288,7 @@ s32 GCMemcard::FZEROGX_MakeSaveGameValid(const Header& cardheader, const DEntry&
return 0; return 0;
// get encrypted destination memory card serial numbers // get encrypted destination memory card serial numbers
cardheader.CARD_GetSerialNo(&serial1, &serial2); const auto [serial1, serial2] = cardheader.CalculateSerial();
// set new serial numbers // set new serial numbers
*(u16*)&FileBuffer[1].m_block[0x0066] = BE16(BE32(serial1) >> 16); *(u16*)&FileBuffer[1].m_block[0x0066] = BE16(BE32(serial1) >> 16);
@ -1332,7 +1334,6 @@ s32 GCMemcard::PSO_MakeSaveGameValid(const Header& cardheader, const DEntry& dir
u32 i, j; u32 i, j;
u32 chksum; u32 chksum;
u32 crc32LUT[256]; u32 crc32LUT[256];
u32 serial1, serial2;
u32 pso3offset = 0x00; u32 pso3offset = 0x00;
// check for PSO1&2 system file // check for PSO1&2 system file
@ -1352,7 +1353,7 @@ s32 GCMemcard::PSO_MakeSaveGameValid(const Header& cardheader, const DEntry& dir
} }
// get encrypted destination memory card serial numbers // get encrypted destination memory card serial numbers
cardheader.CARD_GetSerialNo(&serial1, &serial2); const auto [serial1, serial2] = cardheader.CalculateSerial();
// set new serial numbers // set new serial numbers
*(u32*)&FileBuffer[1].m_block[0x0158] = serial1; *(u32*)&FileBuffer[1].m_block[0x0158] = serial1;
@ -1387,3 +1388,126 @@ s32 GCMemcard::PSO_MakeSaveGameValid(const Header& cardheader, const DEntry& dir
return 1; return 1;
} }
GCMBlock::GCMBlock()
{
Erase();
}
void GCMBlock::Erase()
{
memset(m_block.data(), 0xFF, m_block.size());
}
Header::Header(int slot, u16 size_mbits, bool shift_jis)
{
// 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;
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);
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);
// 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;
FixChecksums();
}
std::pair<u32, u32> Header::CalculateSerial() const
{
static_assert(std::is_trivially_copyable<Header>());
std::array<u8, 32> raw;
memcpy(raw.data(), this, raw.size());
u32 serial1 = 0;
u32 serial2 = 0;
for (size_t i = 0; i < raw.size(); i += 8)
{
serial1 ^= Common::BitCastPtr<u32>(&raw[i + 0]);
serial2 ^= Common::BitCastPtr<u32>(&raw[i + 4]);
}
return std::make_pair(serial1, serial2);
}
DEntry::DEntry()
{
memset(this, 0xFF, DENTRY_SIZE);
}
std::string DEntry::GCI_FileName() const
{
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);
}
void Header::FixChecksums()
{
std::tie(m_checksum, m_checksum_inv) = CalculateChecksums();
}
std::pair<u16, u16> Header::CalculateChecksums() const
{
static_assert(std::is_trivially_copyable<Header>());
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_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);
}
Directory::Directory()
{
memset(this, 0xFF, BLOCK_SIZE);
m_update_counter = 0;
m_checksum = BE16(0xF003);
m_checksum_inv = 0;
}
bool Directory::Replace(const DEntry& entry, size_t index)
{
if (index >= m_dir_entries.size())
return false;
m_dir_entries[index] = entry;
FixChecksums();
return true;
}
void Directory::FixChecksums()
{
std::tie(m_checksum, m_checksum_inv) = CalculateChecksums();
}
std::pair<u16, u16> Directory::CalculateChecksums() const
{
static_assert(std::is_trivially_copyable<Directory>());
std::array<u8, sizeof(Directory)> raw;
memcpy(raw.data(), this, raw.size());
constexpr size_t checksum_area_start = offsetof(Directory, m_dir_entries);
constexpr size_t checksum_area_end = offsetof(Directory, m_checksum);
constexpr size_t checksum_area_size = checksum_area_end - checksum_area_start;
return CalculateMemcardChecksums(&raw[checksum_area_start], checksum_area_size);
}

View File

@ -32,42 +32,84 @@ enum
SLOT_A = 0, SLOT_A = 0,
SLOT_B = 1, SLOT_B = 1,
GCI = 0, GCI = 0,
SUCCESS,
NOMEMCARD,
OPENFAIL,
OUTOFBLOCKS,
OUTOFDIRENTRIES,
LENGTHFAIL,
INVALIDFILESIZE,
TITLEPRESENT,
DIRLEN = 0x7F,
SAV = 0x80, SAV = 0x80,
SAVFAIL,
GCS = 0x110, GCS = 0x110,
GCSFAIL,
FAIL,
WRITEFAIL,
DELETE_FAIL,
MC_FST_BLOCKS = 0x05,
MBIT_TO_BLOCKS = 0x10,
DENTRY_STRLEN = 0x20,
DENTRY_SIZE = 0x40,
BLOCK_SIZE = 0x2000,
BAT_SIZE = 0xFFB,
MemCard59Mb = 0x04,
MemCard123Mb = 0x08,
MemCard251Mb = 0x10,
Memcard507Mb = 0x20,
MemCard1019Mb = 0x40,
MemCard2043Mb = 0x80,
CI8SHARED = 1, CI8SHARED = 1,
RGB5A3, RGB5A3,
CI8, CI8,
}; };
enum class GCMemcardGetSaveDataRetVal
{
SUCCESS,
FAIL,
NOMEMCARD,
};
enum class GCMemcardImportFileRetVal
{
SUCCESS,
FAIL,
NOMEMCARD,
OUTOFDIRENTRIES,
OUTOFBLOCKS,
TITLEPRESENT,
INVALIDFILESIZE,
GCSFAIL,
SAVFAIL,
OPENFAIL,
LENGTHFAIL,
};
enum class GCMemcardExportFileRetVal
{
SUCCESS,
FAIL,
NOMEMCARD,
OPENFAIL,
WRITEFAIL,
UNUSED,
};
enum class GCMemcardRemoveFileRetVal
{
SUCCESS,
NOMEMCARD,
DELETE_FAIL,
};
// size of a single memory card block in bytes
constexpr u32 BLOCK_SIZE = 0x2000;
// the amount of memory card blocks in a megabit of data
constexpr u32 MBIT_TO_BLOCKS = (1024 * 1024) / (BLOCK_SIZE * 8);
// number of metadata and filesystem blocks before the actual user data blocks
constexpr u32 MC_FST_BLOCKS = 0x05;
// maximum number of saves that can be stored on a single memory card
constexpr u8 DIRLEN = 0x7F;
// maximum size of memory card file comment in bytes
constexpr u32 DENTRY_STRLEN = 0x20;
// size of a single entry in the Directory in bytes
constexpr u32 DENTRY_SIZE = 0x40;
// number of block entries in the BAT; one entry uses 2 bytes
constexpr u16 BAT_SIZE = 0xFFB;
// possible sizes of memory cards in megabits
// TODO: Do memory card sizes have to be power of two?
// TODO: Are these all of them? A 4091 block card should work in theory at least.
constexpr u16 MemCard59Mb = 0x04;
constexpr u16 MemCard123Mb = 0x08;
constexpr u16 MemCard251Mb = 0x10;
constexpr u16 Memcard507Mb = 0x20;
constexpr u16 MemCard1019Mb = 0x40;
constexpr u16 MemCard2043Mb = 0x80;
class MemoryCardBase class MemoryCardBase
{ {
public: public:
@ -92,13 +134,11 @@ protected:
struct GCMBlock struct GCMBlock
{ {
GCMBlock() { Erase(); } GCMBlock();
void Erase() { memset(m_block.data(), 0xFF, m_block.size()); } void Erase();
std::array<u8, BLOCK_SIZE> m_block; std::array<u8, BLOCK_SIZE> m_block;
}; };
void calc_checksumsBE(const u16* buf, u32 length, u16* csum, u16* inv_csum);
#pragma pack(push, 1) #pragma pack(push, 1)
struct Header struct Header
{ {
@ -133,6 +173,7 @@ struct Header
std::array<u8, 468> m_unused_1; std::array<u8, 468> m_unused_1;
// 2 bytes at 0x01fa: Update Counter (?, probably unused) // 2 bytes at 0x01fa: Update Counter (?, probably unused)
// TODO: This seems to be 0xFFFF in all my memory cards, might still be part of m_unused_1.
u16 m_update_counter; u16 m_update_counter;
// 2 bytes at 0x01fc: Additive Checksum // 2 bytes at 0x01fc: Additive Checksum
@ -144,59 +185,22 @@ struct Header
// 0x1e00 bytes at 0x0200: Unused (0xff) // 0x1e00 bytes at 0x0200: Unused (0xff)
std::array<u8, 7680> m_unused_2; std::array<u8, 7680> m_unused_2;
void CARD_GetSerialNo(u32* serial1, u32* serial2) const explicit Header(int slot = 0, u16 size_mbits = MemCard2043Mb, bool shift_jis = false);
{
u32 serial[8];
for (int i = 0; i < 8; i++) // Calculates the card serial numbers used for encrypting some save files.
{ std::pair<u32, u32> CalculateSerial() const;
memcpy(&serial[i], (u8*)this + (i * 4), 4);
}
*serial1 = serial[0] ^ serial[2] ^ serial[4] ^ serial[6]; void FixChecksums();
*serial2 = serial[1] ^ serial[3] ^ serial[5] ^ serial[7]; std::pair<u16, u16> CalculateChecksums() const;
}
// Nintendo format algorithm.
// Constants are fixed by the GC SDK
// Changing the constants will break memory card support
explicit Header(int slot = 0, u16 sizeMb = MemCard2043Mb, bool shift_jis = false)
{
memset(this, 0xFF, BLOCK_SIZE);
m_size_mb = sizeMb;
m_encoding = shift_jis ? 1 : 0;
u64 rand = Common::Timer::GetLocalTimeSinceJan1970() - ExpansionInterface::CEXIIPL::GC_EPOCH;
m_format_time = rand;
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);
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);
// 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); static_assert(sizeof(Header) == BLOCK_SIZE);
struct DEntry struct DEntry
{ {
DEntry() { memset(this, 0xFF, DENTRY_SIZE); } DEntry();
std::string GCI_FileName() const
{ // TODO: This probably shouldn't be here at all?
std::string filename = std::string GCI_FileName() const;
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);
}
static constexpr std::array<u8, 4> UNINITIALIZED_GAMECODE{{0xFF, 0xFF, 0xFF, 0xFF}}; static constexpr std::array<u8, 4> UNINITIALIZED_GAMECODE{{0xFF, 0xFF, 0xFF, 0xFF}};
@ -272,24 +276,31 @@ static_assert(sizeof(DEntry) == DENTRY_SIZE);
struct Directory struct Directory
{ {
std::array<DEntry, DIRLEN> m_dir_entries; // 0x0000 Directory Entries (max 127) // 127 files of 0x40 bytes each
std::array<DEntry, DIRLEN> m_dir_entries;
// 0x3a bytes at 0x1fc0: Unused, always 0xFF
std::array<u8, 0x3a> m_padding; std::array<u8, 0x3a> m_padding;
Common::BigEndianValue<u16> m_update_counter; // 0x1ffa 2 Update Counter
u16 m_checksum; // 0x1ffc 2 Additive Checksum // 2 bytes at 0x1ffa: Update Counter
u16 m_checksum_inv; // 0x1ffe 2 Inverse Checksum // TODO: What happens if this overflows? Is there a special case for preferring 0 over max value?
Directory() Common::BigEndianValue<u16> m_update_counter;
{
memset(this, 0xFF, BLOCK_SIZE); // 2 bytes at 0x1ffc: Additive Checksum
m_update_counter = 0; u16 m_checksum;
m_checksum = BE16(0xF003);
m_checksum_inv = 0; // 2 bytes at 0x1ffe: Inverse Checksum
} u16 m_checksum_inv;
void Replace(DEntry d, int idx)
{ // Constructs an empty Directory block.
m_dir_entries[idx] = d; Directory();
fixChecksums();
} // Replaces the file metadata at the given index (range 0-126)
void fixChecksums() { calc_checksumsBE((u16*)this, 0xFFE, &m_checksum, &m_checksum_inv); } // with the given DEntry data.
bool Replace(const DEntry& entry, size_t index);
void FixChecksums();
std::pair<u16, u16> CalculateChecksums() const;
}; };
static_assert(sizeof(Directory) == BLOCK_SIZE); static_assert(sizeof(Directory) == BLOCK_SIZE);
@ -313,37 +324,15 @@ struct BlockAlloc
// 0x1ff8 bytes at 0x000a: Map of allocated Blocks // 0x1ff8 bytes at 0x000a: Map of allocated Blocks
std::array<Common::BigEndianValue<u16>, BAT_SIZE> m_map; std::array<Common::BigEndianValue<u16>, BAT_SIZE> m_map;
u16 GetNextBlock(u16 Block) const; explicit BlockAlloc(u16 size_mbits = MemCard2043Mb);
u16 NextFreeBlock(u16 MaxBlock, u16 StartingBlock = MC_FST_BLOCKS) const;
bool ClearBlocks(u16 StartingBlock, u16 Length); u16 GetNextBlock(u16 block) const;
void fixChecksums() u16 NextFreeBlock(u16 max_block, u16 starting_block = MC_FST_BLOCKS) const;
{ bool ClearBlocks(u16 starting_block, u16 block_count);
calc_checksumsBE((u16*)&m_update_counter, 0xFFE, &m_checksum, &m_checksum_inv); u16 AssignBlocksContiguous(u16 length);
}
explicit BlockAlloc(u16 sizeMb = MemCard2043Mb) void FixChecksums();
{ std::pair<u16, u16> CalculateChecksums() const;
memset(this, 0, BLOCK_SIZE);
m_free_blocks = (sizeMb * MBIT_TO_BLOCKS) - MC_FST_BLOCKS;
m_last_allocated_block = 4;
fixChecksums();
}
u16 AssignBlocksContiguous(u16 length)
{
u16 starting = m_last_allocated_block + 1;
if (length > m_free_blocks)
return 0xFFFF;
u16 current = starting;
while ((current - starting + 1) < length)
{
m_map[current - 5] = current + 1;
current++;
}
m_map[current - 5] = 0xFFFF;
m_last_allocated_block = current;
m_free_blocks = m_free_blocks - length;
fixChecksums();
return starting;
}
}; };
static_assert(sizeof(BlockAlloc) == BLOCK_SIZE); static_assert(sizeof(BlockAlloc) == BLOCK_SIZE);
#pragma pack(pop) #pragma pack(pop)
@ -389,8 +378,7 @@ private:
int m_active_directory; int m_active_directory;
int m_active_bat; int m_active_bat;
u32 ImportGciInternal(File::IOFile&& gci, const std::string& inputFile, GCMemcardImportFileRetVal ImportGciInternal(File::IOFile&& gci, const std::string& inputFile);
const std::string& outputFile);
void InitActiveDirBat(); void InitActiveDirBat();
const Directory& GetActiveDirectory() const; const Directory& GetActiveDirectory() const;
@ -454,26 +442,27 @@ public:
// Fetches a DEntry from the given file index. // Fetches a DEntry from the given file index.
std::optional<DEntry> GetDEntry(u8 index) const; std::optional<DEntry> GetDEntry(u8 index) const;
u32 GetSaveData(u8 index, std::vector<GCMBlock>& saveBlocks) const; GCMemcardGetSaveDataRetVal GetSaveData(u8 index, std::vector<GCMBlock>& saveBlocks) const;
// adds the file to the directory and copies its contents // adds the file to the directory and copies its contents
u32 ImportFile(const DEntry& direntry, std::vector<GCMBlock>& saveBlocks); GCMemcardImportFileRetVal ImportFile(const DEntry& direntry, std::vector<GCMBlock>& saveBlocks);
// delete a file from the directory // delete a file from the directory
u32 RemoveFile(u8 index); GCMemcardRemoveFileRetVal RemoveFile(u8 index);
// reads a save from another memcard, and imports the data into this memcard // reads a save from another memcard, and imports the data into this memcard
u32 CopyFrom(const GCMemcard& source, u8 index); GCMemcardImportFileRetVal CopyFrom(const GCMemcard& source, u8 index);
// reads a .gci/.gcs/.sav file and calls ImportFile or saves out a gci file // reads a .gci/.gcs/.sav file and calls ImportFile
u32 ImportGci(const std::string& inputFile, const std::string& outputFile); GCMemcardImportFileRetVal ImportGci(const std::string& inputFile);
// writes a .gci file to disk containing index // writes a .gci file to disk containing index
u32 ExportGci(u8 index, const std::string& fileName, const std::string& directory) const; GCMemcardExportFileRetVal ExportGci(u8 index, const std::string& fileName,
const std::string& directory) const;
// GCI files are untouched, SAV files are byteswapped // GCI files are untouched, SAV files are byteswapped
// GCS files have the block count set, default is 1 (For export as GCS) // GCS files have the block count set, default is 1 (For export as GCS)
static void Gcs_SavConvert(DEntry& tempDEntry, int saveType, int length = BLOCK_SIZE); static void Gcs_SavConvert(DEntry& tempDEntry, int saveType, u64 length = BLOCK_SIZE);
// reads the banner image // reads the banner image
bool ReadBannerRGBA8(u8 index, u32* buffer) const; bool ReadBannerRGBA8(u8 index, u32* buffer) const;

View File

@ -215,7 +215,7 @@ GCMemcardDirectory::GCMemcardDirectory(const std::string& directory, int slot, u
} }
m_loaded_saves.clear(); m_loaded_saves.clear();
m_dir1.fixChecksums(); m_dir1.FixChecksums();
m_dir2 = m_dir1; m_dir2 = m_dir1;
m_bat2 = m_bat1; m_bat2 = m_bat1;

View File

@ -300,7 +300,9 @@ void GCMemcardManager::ExportFiles(bool prompt)
QStringLiteral("/%1").arg(QString::fromStdString(gci_filename)); QStringLiteral("/%1").arg(QString::fromStdString(gci_filename));
} }
if (!memcard->ExportGci(file_index, path.toStdString(), "")) // TODO: This is obviously intended to check for success instead.
const auto exportRetval = memcard->ExportGci(file_index, path.toStdString(), "");
if (exportRetval == GCMemcardExportFileRetVal::UNUSED)
{ {
File::Delete(path.toStdString()); File::Delete(path.toStdString());
} }
@ -328,9 +330,9 @@ void GCMemcardManager::ImportFile()
if (path.isEmpty()) if (path.isEmpty())
return; return;
const auto result = m_slot_memcard[m_active_slot]->ImportGci(path.toStdString(), ""); const auto result = m_slot_memcard[m_active_slot]->ImportGci(path.toStdString());
if (result != SUCCESS) if (result != GCMemcardImportFileRetVal::SUCCESS)
{ {
ModalMessageBox::critical(this, tr("Import failed"), tr("Failed to import \"%1\".").arg(path)); ModalMessageBox::critical(this, tr("Import failed"), tr("Failed to import \"%1\".").arg(path));
return; return;
@ -356,7 +358,7 @@ void GCMemcardManager::CopyFiles()
const auto result = m_slot_memcard[!m_active_slot]->CopyFrom(*memcard, file_index); const auto result = m_slot_memcard[!m_active_slot]->CopyFrom(*memcard, file_index);
if (result != SUCCESS) if (result != GCMemcardImportFileRetVal::SUCCESS)
{ {
ModalMessageBox::warning(this, tr("Copy failed"), tr("Failed to copy file")); ModalMessageBox::warning(this, tr("Copy failed"), tr("Failed to copy file"));
} }
@ -400,7 +402,7 @@ void GCMemcardManager::DeleteFiles()
for (int file_index : file_indices) for (int file_index : file_indices)
{ {
if (memcard->RemoveFile(file_index) != SUCCESS) if (memcard->RemoveFile(file_index) != GCMemcardRemoveFileRetVal::SUCCESS)
{ {
ModalMessageBox::warning(this, tr("Remove failed"), tr("Failed to remove file")); ModalMessageBox::warning(this, tr("Remove failed"), tr("Failed to remove file"));
} }