diff --git a/pcsx2/Config.h b/pcsx2/Config.h index a9cffb57b3..9e440652a5 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -93,6 +93,17 @@ enum class MemoryCardType MaxCount }; +enum class MemoryCardFileType +{ + Unknown, + PS2_8MB, + PS2_16MB, + PS2_32MB, + PS2_64MB, + PS1, + MaxCount +}; + enum class LimiterModeType : u8 { Nominal, diff --git a/pcsx2/MemoryCardFile.cpp b/pcsx2/MemoryCardFile.cpp index a3e1d0237f..66088f0066 100644 --- a/pcsx2/MemoryCardFile.cpp +++ b/pcsx2/MemoryCardFile.cpp @@ -14,16 +14,16 @@ */ #include "PrecompiledHeader.h" +#include "common/FileSystem.h" #include "common/SafeArray.inl" #include "common/StringUtil.h" #include #include #include +#include #include -struct Component_FileMcd; - #include "MemoryCardFile.h" #include "MemoryCardFolder.h" @@ -40,6 +40,8 @@ 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 @@ -819,3 +821,193 @@ bool isValidNewFilename(wxString filenameStringToTest, wxDirName atBasePath, wxS out_errorMessage = L"[OK - New file name is valid]"; //shouldn't be displayed on success, hence not translatable. return true; } + +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::CombineStdString(path, s_folder_mem_card_id_file)); + return FileSystem::FileExists(superblock_path.c_str()); +} + +std::vector FileMcd_GetAvailableCards(bool include_in_use_cards) +{ + std::vector files; + FileSystem::FindFiles(EmuFolders::MemoryCards.ToUTF8(), "*", + FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &files); + + std::vector mcds; + mcds.reserve(files.size()); + + for (FILESYSTEM_FIND_DATA& fd : files) + { + std::string basename(FileSystem::GetFileNameFromPath(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), MemoryCardType::Folder, + MemoryCardFileType::Unknown, 0u}); + } + else + { + if (fd.Size < MCD_SIZE) + continue; + + mcds.push_back({std::move(basename), std::move(fd.FileName), MemoryCardType::File, + GetMemoryCardFileTypeFromSize(fd.Size), static_cast(fd.Size)}); + } + } + + return mcds; +} + +std::optional FileMcd_GetCardInfo(const std::string_view& name) +{ + std::optional ret; + + std::string basename(name); + std::string path(Path::CombineStdString(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), MemoryCardType::Folder, + MemoryCardFileType::Unknown, 0u}; + } + } + else + { + if (sd.Size >= MCD_SIZE) + { + ret = {std::move(basename), std::move(path), MemoryCardType::File, + GetMemoryCardFileTypeFromSize(sd.Size), static_cast(sd.Size)}; + } + } + + return ret; +} + +bool FileMcd_CreateNewCard(const std::string_view& name, MemoryCardType type, MemoryCardFileType file_type) +{ + const std::string full_path(Path::CombineStdString(EmuFolders::MemoryCards, name)); + + if (type == MemoryCardType::Folder) + { + Console.WriteLn("(FileMcd) Creating new PS2 folder memory card: '%*s'", static_cast(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::CombineStdString(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(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(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; +} diff --git a/pcsx2/MemoryCardFile.h b/pcsx2/MemoryCardFile.h index c2cd11c8b5..4abba25ee1 100644 --- a/pcsx2/MemoryCardFile.h +++ b/pcsx2/MemoryCardFile.h @@ -14,6 +14,10 @@ */ #pragma once +#include "Config.h" +#include +#include +#include struct McdSizeInfo { @@ -23,6 +27,15 @@ struct McdSizeInfo u8 Xor; // Checksum of previous data }; +struct AvailableMcdInfo +{ + std::string name; + std::string path; + MemoryCardType type; + MemoryCardFileType file_type; + u32 size; +}; + extern uint FileMcd_GetMtapPort(uint slot); extern uint FileMcd_GetMtapSlot(uint slot); extern bool FileMcd_IsMultitapSlot(uint slot); @@ -43,3 +56,7 @@ s32 FileMcd_EraseBlock(uint port, uint slot, u32 adr); u64 FileMcd_GetCRC(uint port, uint slot); void FileMcd_NextFrame(uint port, uint slot); bool FileMcd_ReIndex(uint port, uint slot, const wxString& filter); + +std::vector FileMcd_GetAvailableCards(bool include_in_use_cards); +std::optional FileMcd_GetCardInfo(const std::string_view& name); +bool FileMcd_CreateNewCard(const std::string_view& name, MemoryCardType type, MemoryCardFileType file_type); \ No newline at end of file