mirror of https://github.com/PCSX2/pcsx2.git
1063 lines
28 KiB
C++
1063 lines
28 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2010 PCSX2 Dev Team
|
|
*
|
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "PrecompiledHeader.h"
|
|
#include "common/FileSystem.h"
|
|
#include "common/SafeArray.inl"
|
|
#include "common/Path.h"
|
|
#include "common/StringUtil.h"
|
|
|
|
#include <array>
|
|
#include <chrono>
|
|
|
|
#include "MemoryCardFile.h"
|
|
#include "MemoryCardFolder.h"
|
|
|
|
#include "System.h"
|
|
#include "Config.h"
|
|
#include "Host.h"
|
|
#include "IconsFontAwesome5.h"
|
|
|
|
#include "svnrev.h"
|
|
|
|
#include "fmt/core.h"
|
|
|
|
#include <map>
|
|
|
|
static const int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size
|
|
|
|
static const int MC2_MBSIZE = 1024 * 528 * 2; // Size of a single megabyte of card data
|
|
|
|
static const char* s_folder_mem_card_id_file = "_pcsx2_superblock";
|
|
|
|
bool FileMcd_Open = false;
|
|
|
|
// ECC code ported from mymc
|
|
// https://sourceforge.net/p/mymc-opl/code/ci/master/tree/ps2mc_ecc.py
|
|
// Public domain license
|
|
|
|
static u32 CalculateECC(u8* buf)
|
|
{
|
|
const u8 parity_table[256] = {0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,
|
|
0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,
|
|
1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,
|
|
0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,
|
|
1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,
|
|
0,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,1,0,0,1,0,
|
|
1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1,1,0,0,
|
|
1,0,1,1,0};
|
|
|
|
const u8 column_parity_mask[256] = {0,7,22,17,37,34,51,52,52,51,34,37,17,22,
|
|
7,0,67,68,85,82,102,97,112,119,119,112,97,102,82,85,68,67,82,85,68,67,119,112,
|
|
97,102,102,97,112,119,67,68,85,82,17,22,7,0,52,51,34,37,37,34,51,52,0,7,22,17,
|
|
97,102,119,112,68,67,82,85,85,82,67,68,112,119,102,97,34,37,52,51,7,0,17,22,
|
|
22,17,0,7,51,52,37,34,51,52,37,34,22,17,0,7,7,0,17,22,34,37,52,51,112,119,102,
|
|
97,85,82,67,68,68,67,82,85,97,102,119,112,112,119,102,97,85,82,67,68,68,67,82,
|
|
85,97,102,119,112,51,52,37,34,22,17,0,7,7,0,17,22,34,37,52,51,34,37,52,51,7,0,
|
|
17,22,22,17,0,7,51,52,37,34,97,102,119,112,68,67,82,85,85,82,67,68,112,119,102,
|
|
97,17,22,7,0,52,51,34,37,37,34,51,52,0,7,22,17,82,85,68,67,119,112,97,102,102,
|
|
97,112,119,67,68,85,82,67,68,85,82,102,97,112,119,119,112,97,102,82,85,68,67,
|
|
0,7,22,17,37,34,51,52,52,51,34,37,17,22,7,0};
|
|
|
|
u8 column_parity = 0x77;
|
|
u8 line_parity_0 = 0x7F;
|
|
u8 line_parity_1 = 0x7F;
|
|
|
|
for (int i = 0; i < 128; i++)
|
|
{
|
|
u8 b = buf[i];
|
|
column_parity ^= column_parity_mask[b];
|
|
if (parity_table[b])
|
|
{
|
|
line_parity_0 ^= ~i;
|
|
line_parity_1 ^= i;
|
|
}
|
|
}
|
|
|
|
return column_parity | (line_parity_0 << 8) | (line_parity_1 << 16);
|
|
}
|
|
|
|
static bool ConvertNoECCtoRAW(const char* file_in, const char* file_out)
|
|
{
|
|
auto fin = FileSystem::OpenManagedCFile(file_in, "rb");
|
|
if (!fin)
|
|
return false;
|
|
|
|
auto fout = FileSystem::OpenManagedCFile(file_out, "wb");
|
|
if (!fout)
|
|
return false;
|
|
|
|
const s64 size = FileSystem::FSize64(fin.get());
|
|
u8 buffer[512];
|
|
|
|
for (s64 i = 0; i < (size / 512); i++)
|
|
{
|
|
if (std::fread(buffer, sizeof(buffer), 1, fin.get()) != 1 ||
|
|
std::fwrite(buffer, sizeof(buffer), 1, fout.get()) != 1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
u32 checksum = CalculateECC(&buffer[j * 128]);
|
|
if (std::fwrite(&checksum, 3, 1, fout.get()) != 1)
|
|
return false;
|
|
}
|
|
|
|
u32 nullbytes = 0;
|
|
if (std::fwrite(&nullbytes, sizeof(nullbytes), 1, fout.get()) != 1)
|
|
return false;
|
|
}
|
|
|
|
if (std::fflush(fout.get()) != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool ConvertRAWtoNoECC(const char* file_in, const char* file_out)
|
|
{
|
|
auto fin = FileSystem::OpenManagedCFile(file_in, "rb");
|
|
if (!fin)
|
|
return false;
|
|
|
|
auto fout = FileSystem::OpenManagedCFile(file_out, "wb");
|
|
if (!fout)
|
|
return false;
|
|
|
|
const s64 size = FileSystem::FSize64(fin.get());
|
|
u8 buffer[512];
|
|
u8 checksum[16];
|
|
|
|
for (s64 i = 0; i < (size / 528); i++)
|
|
{
|
|
if (std::fread(buffer, sizeof(buffer), 1, fin.get()) != 1 ||
|
|
std::fwrite(buffer, sizeof(buffer), 1, fout.get()) != 1 ||
|
|
std::fread(checksum, sizeof(checksum), 1, fin.get()) != 1)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (std::fflush(fout.get()) != 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// FileMemoryCard
|
|
// --------------------------------------------------------------------------------------
|
|
// Provides thread-safe direct file IO mapping.
|
|
//
|
|
class FileMemoryCard
|
|
{
|
|
protected:
|
|
std::FILE* m_file[8];
|
|
std::string m_filenames[8];
|
|
u8 m_effeffs[528 * 16];
|
|
SafeArray<u8> m_currentdata;
|
|
u64 m_chksum[8];
|
|
bool m_ispsx[8];
|
|
u32 m_chkaddr;
|
|
|
|
public:
|
|
FileMemoryCard();
|
|
virtual ~FileMemoryCard() = default;
|
|
|
|
void Lock();
|
|
void Unlock();
|
|
|
|
void Open();
|
|
void Close();
|
|
|
|
s32 IsPresent(uint slot);
|
|
void GetSizeInfo(uint slot, McdSizeInfo& outways);
|
|
bool IsPSX(uint slot);
|
|
s32 Read(uint slot, u8* dest, u32 adr, int size);
|
|
s32 Save(uint slot, const u8* src, u32 adr, int size);
|
|
s32 EraseBlock(uint slot, u32 adr);
|
|
u64 GetCRC(uint slot);
|
|
|
|
protected:
|
|
bool Seek(std::FILE* f, u32 adr);
|
|
bool Create(const char* mcdFile, uint sizeInMB);
|
|
};
|
|
|
|
uint FileMcd_GetMtapPort(uint slot)
|
|
{
|
|
switch (slot)
|
|
{
|
|
case 0:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
return 0;
|
|
case 1:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
return 1;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
|
|
return 0; // technically unreachable.
|
|
}
|
|
|
|
// Returns the multitap slot number, range 1 to 3 (slot 0 refers to the standard
|
|
// 1st and 2nd player slots).
|
|
uint FileMcd_GetMtapSlot(uint slot)
|
|
{
|
|
switch (slot)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
pxFailDev("Invalid parameter in call to GetMtapSlot -- specified slot is one of the base slots, not a Multitap slot.");
|
|
break;
|
|
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
return slot - 1;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
return slot - 4;
|
|
|
|
jNO_DEFAULT
|
|
}
|
|
|
|
return 0; // technically unreachable.
|
|
}
|
|
|
|
bool FileMcd_IsMultitapSlot(uint slot)
|
|
{
|
|
return (slot > 1);
|
|
}
|
|
|
|
std::string FileMcd_GetDefaultName(uint slot)
|
|
{
|
|
if (FileMcd_IsMultitapSlot(slot))
|
|
return StringUtil::StdStringFromFormat("Mcd-Multitap%u-Slot%02u.ps2", FileMcd_GetMtapPort(slot) + 1, FileMcd_GetMtapSlot(slot) + 1);
|
|
else
|
|
return StringUtil::StdStringFromFormat("Mcd%03u.ps2", slot + 1);
|
|
}
|
|
|
|
FileMemoryCard::FileMemoryCard()
|
|
: m_chkaddr(0)
|
|
{
|
|
memset8<0xff>(m_effeffs);
|
|
}
|
|
|
|
void FileMemoryCard::Open()
|
|
{
|
|
for (int slot = 0; slot < 8; ++slot)
|
|
{
|
|
m_filenames[slot] = {};
|
|
|
|
if (FileMcd_IsMultitapSlot(slot))
|
|
{
|
|
if (!EmuConfig.MultitapPort0_Enabled && (FileMcd_GetMtapPort(slot) == 0))
|
|
continue;
|
|
if (!EmuConfig.MultitapPort1_Enabled && (FileMcd_GetMtapPort(slot) == 1))
|
|
continue;
|
|
}
|
|
|
|
std::string fname(EmuConfig.FullpathToMcd(slot));
|
|
std::string_view str(fname);
|
|
bool cont = false;
|
|
|
|
if (fname.empty())
|
|
{
|
|
str = "[empty filename]";
|
|
cont = true;
|
|
}
|
|
|
|
if (!EmuConfig.Mcd[slot].Enabled)
|
|
{
|
|
str = "[disabled]";
|
|
cont = true;
|
|
}
|
|
|
|
if (EmuConfig.Mcd[slot].Type != MemoryCardType::File)
|
|
{
|
|
str = "[is not memcard file]";
|
|
cont = true;
|
|
}
|
|
|
|
Console.WriteLn(cont ? Color_Gray : Color_Green, "McdSlot %u [File]: %.*s", slot,
|
|
static_cast<int>(str.size()), str.data());
|
|
if (cont)
|
|
continue;
|
|
|
|
if (FileSystem::GetPathFileSize(fname.c_str()) <= 0)
|
|
{
|
|
// FIXME : Ideally this should prompt the user for the size of the
|
|
// memory card file they would like to create, instead of trying to
|
|
// create one automatically.
|
|
|
|
if (!Create(fname.c_str(), 8))
|
|
{
|
|
Host::ReportFormattedErrorAsync("Memory Card", "Could not create a memory card: \n\n%s\n\n",
|
|
fname.c_str());
|
|
}
|
|
}
|
|
|
|
// [TODO] : Add memcard size detection and report it to the console log.
|
|
// (8MB, 256Mb, formatted, unformatted, etc ...)
|
|
|
|
#ifdef _WIN32
|
|
FileSystem::SetPathCompression(fname.c_str(), EmuConfig.McdCompressNTFS);
|
|
#endif
|
|
|
|
if (StringUtil::EndsWith(fname, ".bin"))
|
|
{
|
|
std::string newname(fname + "x");
|
|
if (!ConvertNoECCtoRAW(fname.c_str(), newname.c_str()))
|
|
{
|
|
Console.Error("Could convert memory card: %s", fname.c_str());
|
|
FileSystem::DeleteFilePath(newname.c_str());
|
|
continue;
|
|
}
|
|
|
|
// store the original filename
|
|
m_file[slot] = FileSystem::OpenSharedCFile(newname.c_str(), "r+b", FileSystem::FileShareMode::DenyWrite);
|
|
}
|
|
else
|
|
{
|
|
m_file[slot] = FileSystem::OpenSharedCFile(fname.c_str(), "r+b", FileSystem::FileShareMode::DenyWrite);
|
|
}
|
|
|
|
if (!m_file[slot])
|
|
{
|
|
// Translation note: detailed description should mention that the memory card will be disabled
|
|
// for the duration of this session.
|
|
Host::ReportFormattedErrorAsync("Memory Card", "Access denied to memory card: \n\n%s\n\n"
|
|
"Another instance of PCSX2 may be using this memory card. Close any other instances of PCSX2, or restart your computer.%s",
|
|
fname.c_str(),
|
|
#ifdef WIN32
|
|
"\n\nIf your memory card is in a write-protected folder such as \"Program Files\" or \"Program Files (x86)\", move it to another folder, such as \"Documents\" or \"Desktop\"."
|
|
#else
|
|
""
|
|
#endif
|
|
);
|
|
}
|
|
else // Load checksum
|
|
{
|
|
m_filenames[slot] = std::move(fname);
|
|
m_ispsx[slot] = FileSystem::FSize64(m_file[slot]) == 0x20000;
|
|
m_chkaddr = 0x210;
|
|
|
|
if (!m_ispsx[slot] && FileSystem::FSeek64(m_file[slot], m_chkaddr, SEEK_SET) == 0)
|
|
{
|
|
const size_t read_result = std::fread(&m_chksum[slot], sizeof(m_chksum[slot]), 1, m_file[slot]);
|
|
if (read_result == 0)
|
|
Host::ReportFormattedErrorAsync("Memory Card", "Error reading memcard.\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FileMemoryCard::Close()
|
|
{
|
|
for (int slot = 0; slot < 8; ++slot)
|
|
{
|
|
if (!m_file[slot])
|
|
continue;
|
|
|
|
// Store checksum
|
|
if (!m_ispsx[slot] && FileSystem::FSeek64(m_file[slot], m_chkaddr, SEEK_SET) == 0)
|
|
std::fwrite(&m_chksum[slot], sizeof(m_chksum[slot]), 1, m_file[slot]);
|
|
|
|
std::fclose(m_file[slot]);
|
|
m_file[slot] = nullptr;
|
|
|
|
if (StringUtil::EndsWith(m_filenames[slot], ".bin"))
|
|
{
|
|
const std::string name_in(m_filenames[slot] + 'x');
|
|
if (ConvertRAWtoNoECC(name_in.c_str(), m_filenames[slot].c_str()))
|
|
FileSystem::DeleteFilePath(name_in.c_str());
|
|
}
|
|
|
|
m_filenames[slot] = {};
|
|
}
|
|
}
|
|
|
|
// Returns FALSE if the seek failed (is outside the bounds of the file).
|
|
bool FileMemoryCard::Seek(std::FILE* f, u32 adr)
|
|
{
|
|
const s64 size = FileSystem::FSize64(f);
|
|
|
|
// If anyone knows why this filesize logic is here (it appears to be related to legacy PSX
|
|
// cards, perhaps hacked support for some special emulator-specific memcard formats that
|
|
// had header info?), then please replace this comment with something useful. Thanks! -- air
|
|
|
|
u32 offset = 0;
|
|
|
|
if (size == MCD_SIZE + 64)
|
|
offset = 64;
|
|
else if (size == MCD_SIZE + 3904)
|
|
offset = 3904;
|
|
else
|
|
{
|
|
// perform sanity checks here?
|
|
}
|
|
|
|
return (FileSystem::FSeek64(f, adr + offset, SEEK_SET) == 0);
|
|
}
|
|
|
|
// returns FALSE if an error occurred (either permission denied or disk full)
|
|
bool FileMemoryCard::Create(const char* mcdFile, uint sizeInMB)
|
|
{
|
|
//int enc[16] = {0x77,0x7f,0x7f,0x77,0x7f,0x7f,0x77,0x7f,0x7f,0x77,0x7f,0x7f,0,0,0,0};
|
|
|
|
Console.WriteLn("(FileMcd) Creating new %uMB memory card: %s", sizeInMB, mcdFile);
|
|
|
|
auto fp = FileSystem::OpenManagedCFile(mcdFile, "wb");
|
|
if (!fp)
|
|
return false;
|
|
|
|
for (uint i = 0; i < (MC2_MBSIZE * sizeInMB) / sizeof(m_effeffs); i++)
|
|
{
|
|
if (std::fwrite(m_effeffs, sizeof(m_effeffs), 1, fp.get()) != 1)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
s32 FileMemoryCard::IsPresent(uint slot)
|
|
{
|
|
return m_file[slot] != nullptr;
|
|
}
|
|
|
|
void FileMemoryCard::GetSizeInfo(uint slot, McdSizeInfo& outways)
|
|
{
|
|
outways.SectorSize = 512; // 0x0200
|
|
outways.EraseBlockSizeInSectors = 16; // 0x0010
|
|
outways.Xor = 18; // 0x12, XOR 02 00 00 10
|
|
|
|
if (pxAssert(m_file[slot]))
|
|
outways.McdSizeInSectors = static_cast<u32>(FileSystem::FSize64(m_file[slot])) / (outways.SectorSize + outways.EraseBlockSizeInSectors);
|
|
else
|
|
outways.McdSizeInSectors = 0x4000;
|
|
|
|
u8* pdata = (u8*)&outways.McdSizeInSectors;
|
|
outways.Xor ^= pdata[0] ^ pdata[1] ^ pdata[2] ^ pdata[3];
|
|
}
|
|
|
|
bool FileMemoryCard::IsPSX(uint slot)
|
|
{
|
|
return m_ispsx[slot];
|
|
}
|
|
|
|
s32 FileMemoryCard::Read(uint slot, u8* dest, u32 adr, int size)
|
|
{
|
|
std::FILE* mcfp = m_file[slot];
|
|
if (!mcfp)
|
|
{
|
|
DevCon.Error("(FileMcd) Ignoring attempted read from disabled slot.");
|
|
memset(dest, 0, size);
|
|
return 1;
|
|
}
|
|
if (!Seek(mcfp, adr))
|
|
return 0;
|
|
return std::fread(dest, size, 1, mcfp) == 1;
|
|
}
|
|
|
|
s32 FileMemoryCard::Save(uint slot, const u8* src, u32 adr, int size)
|
|
{
|
|
std::FILE* mcfp = m_file[slot];
|
|
|
|
if (!mcfp)
|
|
{
|
|
DevCon.Error("(FileMcd) Ignoring attempted save/write to disabled slot.");
|
|
return 1;
|
|
}
|
|
|
|
if (m_ispsx[slot])
|
|
{
|
|
m_currentdata.MakeRoomFor(size);
|
|
for (int i = 0; i < size; i++)
|
|
m_currentdata[i] = src[i];
|
|
}
|
|
else
|
|
{
|
|
if (!Seek(mcfp, adr))
|
|
return 0;
|
|
m_currentdata.MakeRoomFor(size);
|
|
|
|
const size_t read_result = std::fread(m_currentdata.GetPtr(), size, 1, mcfp);
|
|
if (read_result == 0)
|
|
Host::ReportFormattedErrorAsync("Memory Card", "Error reading memcard.\n");
|
|
|
|
for (int i = 0; i < size; i++)
|
|
{
|
|
if ((m_currentdata[i] & src[i]) != src[i])
|
|
Console.Warning("(FileMcd) Warning: writing to uncleared data. (%d) [%08X]", slot, adr);
|
|
m_currentdata[i] &= src[i];
|
|
}
|
|
|
|
// Checksumness
|
|
{
|
|
if (adr == m_chkaddr)
|
|
Console.Warning("(FileMcd) Warning: checksum sector overwritten. (%d)", slot);
|
|
|
|
u64* pdata = (u64*)&m_currentdata[0];
|
|
u32 loops = size / 8;
|
|
|
|
for (u32 i = 0; i < loops; i++)
|
|
m_chksum[slot] ^= pdata[i];
|
|
}
|
|
}
|
|
|
|
if (!Seek(mcfp, adr))
|
|
return 0;
|
|
|
|
if (std::fwrite(m_currentdata.GetPtr(), size, 1, mcfp) == 1)
|
|
{
|
|
static auto last = std::chrono::time_point<std::chrono::system_clock>();
|
|
|
|
std::chrono::duration<float> elapsed = std::chrono::system_clock::now() - last;
|
|
if (elapsed > std::chrono::seconds(5))
|
|
{
|
|
Host::AddIconOSDMessage(fmt::format("MemoryCardSave{}", slot), ICON_FA_SD_CARD,
|
|
fmt::format("Memory card '{}' was saved to storage.", Path::GetFileName(m_filenames[slot])),
|
|
Host::OSD_INFO_DURATION);
|
|
last = std::chrono::system_clock::now();
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
s32 FileMemoryCard::EraseBlock(uint slot, u32 adr)
|
|
{
|
|
std::FILE* mcfp = m_file[slot];
|
|
if (!mcfp)
|
|
{
|
|
DevCon.Error("MemoryCard: Ignoring erase for disabled slot.");
|
|
return 1;
|
|
}
|
|
|
|
if (!Seek(mcfp, adr))
|
|
return 0;
|
|
return std::fwrite(m_effeffs, sizeof(m_effeffs), 1, mcfp) == 1;
|
|
}
|
|
|
|
u64 FileMemoryCard::GetCRC(uint slot)
|
|
{
|
|
std::FILE* mcfp = m_file[slot];
|
|
if (!mcfp)
|
|
return 0;
|
|
|
|
u64 retval = 0;
|
|
|
|
if (m_ispsx[slot])
|
|
{
|
|
if (!Seek(mcfp, 0))
|
|
return 0;
|
|
|
|
const s64 mcfpsize = FileSystem::FSize64(mcfp);
|
|
if (mcfpsize < 0)
|
|
return 0;
|
|
|
|
// Process the file in 4k chunks. Speeds things up significantly.
|
|
|
|
u64 buffer[528 * 8]; // use 528 (sector size), ensures even divisibility
|
|
|
|
const uint filesize = static_cast<uint>(mcfpsize) / sizeof(buffer);
|
|
for (uint i = filesize; i; --i)
|
|
{
|
|
if (std::fread(buffer, sizeof(buffer), 1, mcfp) != 1)
|
|
return 0;
|
|
|
|
for (uint t = 0; t < std::size(buffer); ++t)
|
|
retval ^= buffer[t];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
retval = m_chksum[slot];
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// MemoryCard Component API Bindings
|
|
// --------------------------------------------------------------------------------------
|
|
namespace Mcd
|
|
{
|
|
FileMemoryCard impl; // class-based implementations we refer to when API is invoked
|
|
FolderMemoryCardAggregator implFolder;
|
|
}; // namespace Mcd
|
|
|
|
uint FileMcd_ConvertToSlot(uint port, uint slot)
|
|
{
|
|
if (slot == 0)
|
|
return port;
|
|
if (port == 0)
|
|
return slot + 1; // multitap 1
|
|
return slot + 4; // multitap 2
|
|
}
|
|
|
|
void FileMcd_EmuOpen()
|
|
{
|
|
if(FileMcd_Open)
|
|
return;
|
|
FileMcd_Open = true;
|
|
// detect inserted memory card types
|
|
for (uint slot = 0; slot < 8; ++slot)
|
|
{
|
|
if (EmuConfig.Mcd[slot].Filename.empty())
|
|
{
|
|
EmuConfig.Mcd[slot].Type = MemoryCardType::Empty;
|
|
}
|
|
else if (EmuConfig.Mcd[slot].Enabled)
|
|
{
|
|
MemoryCardType type = MemoryCardType::File; // default to file if we can't find anything at the path so it gets auto-generated
|
|
|
|
const std::string path(EmuConfig.FullpathToMcd(slot));
|
|
if (FileSystem::DirectoryExists(path.c_str()))
|
|
type = MemoryCardType::Folder;
|
|
|
|
EmuConfig.Mcd[slot].Type = type;
|
|
}
|
|
}
|
|
|
|
Mcd::impl.Open();
|
|
Mcd::implFolder.SetFiltering(EmuConfig.McdFolderAutoManage);
|
|
Mcd::implFolder.Open();
|
|
}
|
|
|
|
void FileMcd_EmuClose()
|
|
{
|
|
if(!FileMcd_Open)
|
|
return;
|
|
FileMcd_Open = false;
|
|
Mcd::implFolder.Close();
|
|
Mcd::impl.Close();
|
|
}
|
|
|
|
s32 FileMcd_IsPresent(uint port, uint slot)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
case MemoryCardType::File:
|
|
return Mcd::impl.IsPresent(combinedSlot);
|
|
case MemoryCardType::Folder:
|
|
return Mcd::implFolder.IsPresent(combinedSlot);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void FileMcd_GetSizeInfo(uint port, uint slot, McdSizeInfo* outways)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
case MemoryCardType::File:
|
|
Mcd::impl.GetSizeInfo(combinedSlot, *outways);
|
|
break;
|
|
case MemoryCardType::Folder:
|
|
Mcd::implFolder.GetSizeInfo(combinedSlot, *outways);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool FileMcd_IsPSX(uint port, uint slot)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
case MemoryCardType::File:
|
|
return Mcd::impl.IsPSX(combinedSlot);
|
|
case MemoryCardType::Folder:
|
|
return Mcd::implFolder.IsPSX(combinedSlot);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
s32 FileMcd_Read(uint port, uint slot, u8* dest, u32 adr, int size)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
case MemoryCardType::File:
|
|
return Mcd::impl.Read(combinedSlot, dest, adr, size);
|
|
case MemoryCardType::Folder:
|
|
return Mcd::implFolder.Read(combinedSlot, dest, adr, size);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
s32 FileMcd_Save(uint port, uint slot, const u8* src, u32 adr, int size)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
case MemoryCardType::File:
|
|
return Mcd::impl.Save(combinedSlot, src, adr, size);
|
|
case MemoryCardType::Folder:
|
|
return Mcd::implFolder.Save(combinedSlot, src, adr, size);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
s32 FileMcd_EraseBlock(uint port, uint slot, u32 adr)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
case MemoryCardType::File:
|
|
return Mcd::impl.EraseBlock(combinedSlot, adr);
|
|
case MemoryCardType::Folder:
|
|
return Mcd::implFolder.EraseBlock(combinedSlot, adr);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
u64 FileMcd_GetCRC(uint port, uint slot)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
case MemoryCardType::File:
|
|
return Mcd::impl.GetCRC(combinedSlot);
|
|
case MemoryCardType::Folder:
|
|
return Mcd::implFolder.GetCRC(combinedSlot);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void FileMcd_NextFrame(uint port, uint slot)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
//case MemoryCardType::MemoryCard_File:
|
|
// Mcd::impl.NextFrame( combinedSlot );
|
|
// break;
|
|
case MemoryCardType::Folder:
|
|
Mcd::implFolder.NextFrame(combinedSlot);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool FileMcd_ReIndex(uint port, uint slot, const std::string& filter)
|
|
{
|
|
const uint combinedSlot = FileMcd_ConvertToSlot(port, slot);
|
|
switch (EmuConfig.Mcd[combinedSlot].Type)
|
|
{
|
|
//case MemoryCardType::File:
|
|
// return Mcd::impl.ReIndex( combinedSlot, filter );
|
|
// break;
|
|
case MemoryCardType::Folder:
|
|
return Mcd::implFolder.ReIndex(combinedSlot, EmuConfig.McdFolderAutoManage, filter);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// Library API Implementations
|
|
// --------------------------------------------------------------------------------------
|
|
|
|
static MemoryCardFileType GetMemoryCardFileTypeFromSize(s64 size)
|
|
{
|
|
if (size == (8 * MC2_MBSIZE))
|
|
return MemoryCardFileType::PS2_8MB;
|
|
else if (size == (16 * MC2_MBSIZE))
|
|
return MemoryCardFileType::PS2_16MB;
|
|
else if (size == (32 * MC2_MBSIZE))
|
|
return MemoryCardFileType::PS2_32MB;
|
|
else if (size == (64 * MC2_MBSIZE))
|
|
return MemoryCardFileType::PS2_64MB;
|
|
else if (size == MCD_SIZE)
|
|
return MemoryCardFileType::PS1;
|
|
else
|
|
return MemoryCardFileType::Unknown;
|
|
}
|
|
|
|
static bool IsMemoryCardFolder(const std::string& path)
|
|
{
|
|
const std::string superblock_path(Path::Combine(path, s_folder_mem_card_id_file));
|
|
return FileSystem::FileExists(superblock_path.c_str());
|
|
}
|
|
|
|
static bool IsMemoryCardFormatted(const std::string& path)
|
|
{
|
|
auto fp = FileSystem::OpenManagedSharedCFile(path.c_str(), "rb", FileSystem::FileShareMode::DenyNone);
|
|
if (!fp)
|
|
return false;
|
|
|
|
static const char formatted_psx[] = "MC";
|
|
static const char formatted_string[] = "Sony PS2 Memory Card Format";
|
|
static constexpr size_t read_length = sizeof(formatted_string) - 1;
|
|
|
|
u8 data[read_length];
|
|
if (std::fread(data, read_length, 1, fp.get()) != 1)
|
|
return false;
|
|
|
|
return (std::memcmp(data, formatted_string, sizeof(formatted_string) - 1) == 0 ||
|
|
std::memcmp(data, formatted_psx, sizeof(formatted_psx) - 1) == 0);
|
|
}
|
|
|
|
std::vector<AvailableMcdInfo> FileMcd_GetAvailableCards(bool include_in_use_cards)
|
|
{
|
|
std::vector<FILESYSTEM_FIND_DATA> files;
|
|
FileSystem::FindFiles(EmuFolders::MemoryCards.c_str(), "*",
|
|
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &files);
|
|
|
|
std::vector<AvailableMcdInfo> mcds;
|
|
mcds.reserve(files.size());
|
|
|
|
for (FILESYSTEM_FIND_DATA& fd : files)
|
|
{
|
|
std::string basename(Path::GetFileName(fd.FileName));
|
|
if (!include_in_use_cards)
|
|
{
|
|
bool in_use = false;
|
|
for (size_t i = 0; i < std::size(EmuConfig.Mcd); i++)
|
|
{
|
|
if (EmuConfig.Mcd[i].Filename == basename)
|
|
{
|
|
in_use = true;
|
|
break;
|
|
}
|
|
}
|
|
if (in_use)
|
|
continue;
|
|
}
|
|
|
|
if (fd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
if (!IsMemoryCardFolder(fd.FileName))
|
|
continue;
|
|
|
|
mcds.push_back({std::move(basename), std::move(fd.FileName), fd.ModificationTime,
|
|
MemoryCardType::Folder, MemoryCardFileType::Unknown, 0u, true});
|
|
}
|
|
else
|
|
{
|
|
if (fd.Size < MCD_SIZE)
|
|
continue;
|
|
|
|
const bool formatted = IsMemoryCardFormatted(fd.FileName);
|
|
mcds.push_back({std::move(basename), std::move(fd.FileName), fd.ModificationTime,
|
|
MemoryCardType::File, GetMemoryCardFileTypeFromSize(fd.Size),
|
|
static_cast<u32>(fd.Size), formatted});
|
|
}
|
|
}
|
|
|
|
return mcds;
|
|
}
|
|
|
|
std::optional<AvailableMcdInfo> FileMcd_GetCardInfo(const std::string_view& name)
|
|
{
|
|
std::optional<AvailableMcdInfo> ret;
|
|
|
|
std::string basename(name);
|
|
std::string path(Path::Combine(EmuFolders::MemoryCards, basename));
|
|
|
|
FILESYSTEM_STAT_DATA sd;
|
|
if (!FileSystem::StatFile(path.c_str(), &sd))
|
|
return ret;
|
|
|
|
if (sd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
if (IsMemoryCardFolder(path))
|
|
{
|
|
ret = {std::move(basename), std::move(path), sd.ModificationTime,
|
|
MemoryCardType::Folder, MemoryCardFileType::Unknown, 0u, true};
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (sd.Size >= MCD_SIZE)
|
|
{
|
|
const bool formatted = IsMemoryCardFormatted(path);
|
|
ret = {std::move(basename), std::move(path), sd.ModificationTime,
|
|
MemoryCardType::File, GetMemoryCardFileTypeFromSize(sd.Size),
|
|
static_cast<u32>(sd.Size), formatted};
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool FileMcd_CreateNewCard(const std::string_view& name, MemoryCardType type, MemoryCardFileType file_type)
|
|
{
|
|
const std::string full_path(Path::Combine(EmuFolders::MemoryCards, name));
|
|
|
|
if (type == MemoryCardType::Folder)
|
|
{
|
|
Console.WriteLn("(FileMcd) Creating new PS2 folder memory card: '%.*s'", static_cast<int>(name.size()), name.data());
|
|
|
|
if (!FileSystem::CreateDirectoryPath(full_path.c_str(), false))
|
|
{
|
|
Host::ReportFormattedErrorAsync("Memory Card Creation Failed", "Failed to create directory '%s'.", full_path.c_str());
|
|
return false;
|
|
}
|
|
|
|
// write the superblock
|
|
auto fp = FileSystem::OpenManagedCFile(Path::Combine(full_path, s_folder_mem_card_id_file).c_str(), "wb");
|
|
if (!fp)
|
|
{
|
|
Host::ReportFormattedErrorAsync("Memory Card Creation Failed", "Failed to write memory card folder superblock '%s'.", full_path.c_str());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (type == MemoryCardType::File)
|
|
{
|
|
if (file_type <= MemoryCardFileType::Unknown || file_type >= MemoryCardFileType::MaxCount)
|
|
return false;
|
|
|
|
static constexpr std::array<u32, static_cast<size_t>(MemoryCardFileType::MaxCount)> sizes = {{0, 8 * MC2_MBSIZE, 16 * MC2_MBSIZE, 32 * MC2_MBSIZE, 64 * MC2_MBSIZE, MCD_SIZE}};
|
|
|
|
const bool isPSX = (type == MemoryCardType::File && file_type == MemoryCardFileType::PS1);
|
|
const u32 size = sizes[static_cast<u32>(file_type)];
|
|
if (!isPSX && size == 0)
|
|
return false;
|
|
|
|
auto fp = FileSystem::OpenManagedCFile(full_path.c_str(), "wb");
|
|
if (!fp)
|
|
{
|
|
Host::ReportFormattedErrorAsync("Memory Card Creation Failed", "Failed to open file '%s'.", full_path.c_str());
|
|
return false;
|
|
}
|
|
|
|
if (!isPSX)
|
|
{
|
|
Console.WriteLn("(FileMcd) Creating new PS2 %uMB memory card: '%s'", size / MC2_MBSIZE, full_path.c_str());
|
|
|
|
// PS2 Memory Card
|
|
u8 m_effeffs[528 * 16];
|
|
memset8<0xff>(m_effeffs);
|
|
|
|
const u32 count = size / sizeof(m_effeffs);
|
|
for (uint i = 0; i < count; i++)
|
|
{
|
|
if (std::fwrite(m_effeffs, sizeof(m_effeffs), 1, fp.get()) != 1)
|
|
{
|
|
Host::ReportFormattedErrorAsync("Memory Card Creation Failed", "Failed to write file '%s'.", full_path.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLn("(FileMcd) Creating new PSX 128 KiB memory card: '%s'", full_path.c_str());
|
|
|
|
// PSX Memory Card; 8192 is the size in bytes of a single block of a PSX memory card (8 KiB).
|
|
u8 m_effeffs_psx[8192];
|
|
memset8<0xff>(m_effeffs_psx);
|
|
|
|
// PSX cards consist of 16 blocks, each 8 KiB in size.
|
|
for (uint i = 0; i < 16; i++)
|
|
{
|
|
if (std::fwrite(m_effeffs_psx, sizeof(m_effeffs_psx), 1, fp.get()) != 1)
|
|
{
|
|
Host::ReportFormattedErrorAsync("Memory Card Creation Failed", "Failed to write file '%s'.", full_path.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FileMcd_RenameCard(const std::string_view& name, const std::string_view& new_name)
|
|
{
|
|
const std::string name_path(Path::Combine(EmuFolders::MemoryCards, name));
|
|
const std::string new_name_path(Path::Combine(EmuFolders::MemoryCards, new_name));
|
|
|
|
FILESYSTEM_STAT_DATA sd, new_sd;
|
|
if (!FileSystem::StatFile(name_path.c_str(), &sd) || FileSystem::StatFile(new_name_path.c_str(), &new_sd))
|
|
{
|
|
Console.Error("(FileMcd) New name already exists, or old name does not");
|
|
return false;
|
|
}
|
|
|
|
Console.WriteLn("(FileMcd) Renaming memory card '%.*s' to '%.*s'",
|
|
static_cast<int>(name.size()), name.data(),
|
|
static_cast<int>(new_name.size()), new_name.data());
|
|
|
|
if (!FileSystem::RenamePath(name_path.c_str(), new_name_path.c_str()))
|
|
{
|
|
Console.Error("(FileMcd) Failed to rename '%s' to '%s'", name_path.c_str(), new_name_path.c_str());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileMcd_DeleteCard(const std::string_view& name)
|
|
{
|
|
const std::string name_path(Path::Combine(EmuFolders::MemoryCards, name));
|
|
|
|
FILESYSTEM_STAT_DATA sd;
|
|
if (!FileSystem::StatFile(name_path.c_str(), &sd))
|
|
{
|
|
Console.Error("(FileMcd) Can't stat '%s' for deletion", name_path.c_str());
|
|
return false;
|
|
}
|
|
|
|
Console.WriteLn("(FileMcd) Deleting memory card '%.*s'", static_cast<int>(name.size()), name.data());
|
|
|
|
if (sd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
// must be a folder memcard, so do a recursive delete (scary)
|
|
if (!FileSystem::RecursiveDeleteDirectory(name_path.c_str()))
|
|
{
|
|
Console.Error("(FileMcd) Failed to recursively delete '%s'", name_path.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!FileSystem::DeleteFilePath(name_path.c_str()))
|
|
{
|
|
Console.Error("(FileMcd) Failed to delete file '%s'", name_path.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|