From 909b6d8eb6677f6a5c50fac74ed40b9e5b45d5b0 Mon Sep 17 00:00:00 2001 From: Ty Lamontagne Date: Sun, 12 Jan 2025 17:51:15 -0500 Subject: [PATCH 1/3] Memory Cards: Use file memory mapping instead of explicit file I/O --- pcsx2/SIO/Memcard/MemoryCardFile.cpp | 51 ++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/pcsx2/SIO/Memcard/MemoryCardFile.cpp b/pcsx2/SIO/Memcard/MemoryCardFile.cpp index 032cef212a..b89083ce58 100644 --- a/pcsx2/SIO/Memcard/MemoryCardFile.cpp +++ b/pcsx2/SIO/Memcard/MemoryCardFile.cpp @@ -24,6 +24,11 @@ #include +#ifdef _WIN32 +#include +#include +#endif + static constexpr int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size static constexpr int MC2_MBSIZE = 1024 * 528 * 2; // Size of a single megabyte of card data @@ -157,6 +162,8 @@ class FileMemoryCard { protected: std::FILE* m_file[8] = {}; + u8* m_mappings[8] = {}; + s64 m_fileSize[8] = {}; std::string m_filenames[8] = {}; std::vector m_currentdata; @@ -337,6 +344,21 @@ void FileMemoryCard::Open() if (read_result == 0) Host::ReportErrorAsync("Memory Card Read Failed", "Error reading memory card."); } + +#ifdef _WIN32 + const int fd = _fileno(m_file[slot]); + // Doesn't leak, refcounted based on views + const HANDLE hMapping = CreateFileMapping(reinterpret_cast(_get_osfhandle(fd)), nullptr, PAGE_READWRITE, 0, 0, nullptr); + if (!hMapping) + { + Console.Warning("CreateFileMapping failed: %d", GetLastError()); + } + m_mappings[slot] = reinterpret_cast(MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + if (!m_mappings[slot]) + { + Console.Warning("MapViewOfFile failed: %d", GetLastError()); + } +#endif } } } @@ -364,6 +386,13 @@ void FileMemoryCard::Close() m_filenames[slot] = {}; m_fileSize[slot] = -1; +#ifdef _WIN32 + if (m_mappings[slot]) + { + UnmapViewOfFile(m_mappings[slot]); + m_mappings[slot] = nullptr; + } +#endif } } @@ -430,9 +459,20 @@ s32 FileMemoryCard::Read(uint slot, u8* dest, u32 adr, int size) memset(dest, 0, size); return 1; } + +#ifdef _WIN32 + if (adr + size > static_cast(m_fileSize[slot])) + { + Console.Warning("(FileMcd) Warning: read past end of file. (%d) [%08X]", slot, adr); + } + + std::memcpy(dest, m_mappings[slot] + adr, size); + return 1; +#else if (!Seek(mcfp, adr)) return 0; return std::fread(dest, size, 1, mcfp) == 1; +#endif } s32 FileMemoryCard::Save(uint slot, const u8* src, u32 adr, int size) @@ -516,10 +556,15 @@ s32 FileMemoryCard::EraseBlock(uint slot, u32 adr) if (!Seek(mcfp, adr)) return 0; +#ifdef _WIN32 + std::memset(m_mappings[slot] + adr, 0xff, MC2_ERASE_SIZE); - u8 buf[MC2_ERASE_SIZE]; - std::memset(buf, 0xff, sizeof(buf)); - return std::fwrite(buf, sizeof(buf), 1, mcfp) == 1; + return 1; +#else + std::array buffer; + std::memset(buffer.data(), 0xff, buffer.size()); + return std::fwrite(buffer.data(), buffer.size(), 1, mcfp) == 1; +#endif } u64 FileMemoryCard::GetCRC(uint slot) From 4d600dc1d4782d4fece55714df21f7c290fc2ccc Mon Sep 17 00:00:00 2001 From: Ty Lamontagne Date: Sun, 12 Jan 2025 22:49:39 -0500 Subject: [PATCH 2/3] Memory Cards: mmap on Linux and MacOS --- common/Darwin/DarwinMisc.cpp | 18 ++++++++++++++++++ common/HostSys.h | 5 +++++ common/Linux/LnxHostSys.cpp | 16 ++++++++++++++++ common/Windows/WinHostSys.cpp | 17 +++++++++++++++++ pcsx2/SIO/Memcard/MemoryCardFile.cpp | 27 ++++++++++----------------- 5 files changed, 66 insertions(+), 17 deletions(-) diff --git a/common/Darwin/DarwinMisc.cpp b/common/Darwin/DarwinMisc.cpp index a4f01ac13d..73c835f67d 100644 --- a/common/Darwin/DarwinMisc.cpp +++ b/common/Darwin/DarwinMisc.cpp @@ -291,6 +291,24 @@ void* HostSys::CreateSharedMemory(const char* name, size_t size) return reinterpret_cast(static_cast(port)); } +void* HostSys::CreateMappingFromFile(FILE* file) +{ + return reinterpret_cast(static_cast(fileno(file))); +} + +void* HostSys::MapMapping(void* handle, const PageProtectionMode& mode) +{ + const u32 mmap_prot = (mode.CanWrite() ? (PROT_READ | PROT_WRITE) : (PROT_READ)) | (mode.CanExecute() ? PROT_EXEC : 0); + + return mmap(nullptr, 0, mmap_prot, MAP_PRIVATE | MAP_ANON, static_cast(reinterpret_cast(handle)), 0); +} + +void HostSys::DestroyMapping(void* handle) +{ + // The handle mmap requires is the same as the file descriptor. + return; +} + void HostSys::DestroySharedMemory(void* ptr) { mach_port_deallocate(mach_task_self(), static_cast(reinterpret_cast(ptr))); diff --git a/common/HostSys.h b/common/HostSys.h index 23646284d9..68d9893a11 100644 --- a/common/HostSys.h +++ b/common/HostSys.h @@ -101,6 +101,11 @@ namespace HostSys extern std::string GetFileMappingName(const char* prefix); extern void* CreateSharedMemory(const char* name, size_t size); + + extern void* CreateMappingFromFile(FILE* file); + extern void* MapMapping(void* handle, const PageProtectionMode& mode); + extern void DestroyMapping(void* handle); + extern void DestroySharedMemory(void* ptr); extern void* MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode); extern void UnmapSharedMemory(void* baseaddr, size_t size); diff --git a/common/Linux/LnxHostSys.cpp b/common/Linux/LnxHostSys.cpp index 54a8e2f24a..c28b6fe1f3 100644 --- a/common/Linux/LnxHostSys.cpp +++ b/common/Linux/LnxHostSys.cpp @@ -115,6 +115,22 @@ void* HostSys::CreateSharedMemory(const char* name, size_t size) return reinterpret_cast(static_cast(fd)); } +void* HostSys::CreateMappingFromFile(FILE* file) +{ + return reinterpret_cast(static_cast(fileno(file))); +} + +void* HostSys::MapMapping(void* handle, const PageProtectionMode& mode) +{ + return HostSys::MapSharedMemory(handle, 0, nullptr, 0, mode); +} + +void HostSys::DestroyMapping(void* handle) +{ + // The handle mmap requires is the same as the file descriptor. + return; +} + void HostSys::DestroySharedMemory(void* ptr) { close(static_cast(reinterpret_cast(ptr))); diff --git a/common/Windows/WinHostSys.cpp b/common/Windows/WinHostSys.cpp index 54e76fa6cc..2fe1479d87 100644 --- a/common/Windows/WinHostSys.cpp +++ b/common/Windows/WinHostSys.cpp @@ -13,6 +13,7 @@ #include "fmt/core.h" #include "fmt/format.h" +#include #include static DWORD ConvertToWinApi(const PageProtectionMode& mode) @@ -72,6 +73,22 @@ void* HostSys::CreateSharedMemory(const char* name, size_t size) static_cast(size >> 32), static_cast(size), StringUtil::UTF8StringToWideString(name).c_str())); } +void* HostSys::CreateMappingFromFile(FILE* fd) +{ + return static_cast(CreateFileMappingW(reinterpret_cast(_get_osfhandle(_fileno(fd))), NULL, PAGE_READWRITE, + 0, 0, nullptr)); +} + +void* HostSys::MapMapping(void* handle, const PageProtectionMode& mode) +{ + return MapViewOfFile(static_cast(handle), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); +} + +void HostSys::DestroyMapping(void* handle) +{ + CloseHandle(static_cast(handle)); +} + void HostSys::DestroySharedMemory(void* ptr) { CloseHandle(static_cast(ptr)); diff --git a/pcsx2/SIO/Memcard/MemoryCardFile.cpp b/pcsx2/SIO/Memcard/MemoryCardFile.cpp index b89083ce58..ebde768cb3 100644 --- a/pcsx2/SIO/Memcard/MemoryCardFile.cpp +++ b/pcsx2/SIO/Memcard/MemoryCardFile.cpp @@ -10,6 +10,7 @@ #include "common/Console.h" #include "common/Error.h" #include "common/FileSystem.h" +#include "common/HostSys.h" #include "common/Path.h" #include "common/StringUtil.h" @@ -24,11 +25,6 @@ #include -#ifdef _WIN32 -#include -#include -#endif - static constexpr int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size static constexpr int MC2_MBSIZE = 1024 * 528 * 2; // Size of a single megabyte of card data @@ -163,6 +159,7 @@ class FileMemoryCard protected: std::FILE* m_file[8] = {}; u8* m_mappings[8] = {}; + void* m_mapping_handles[8] = {}; s64 m_fileSize[8] = {}; std::string m_filenames[8] = {}; @@ -345,20 +342,16 @@ void FileMemoryCard::Open() Host::ReportErrorAsync("Memory Card Read Failed", "Error reading memory card."); } -#ifdef _WIN32 - const int fd = _fileno(m_file[slot]); - // Doesn't leak, refcounted based on views - const HANDLE hMapping = CreateFileMapping(reinterpret_cast(_get_osfhandle(fd)), nullptr, PAGE_READWRITE, 0, 0, nullptr); - if (!hMapping) + m_mapping_handles[slot] = HostSys::CreateMappingFromFile(m_file[slot]); + if (!m_mapping_handles[slot]) { - Console.Warning("CreateFileMapping failed: %d", GetLastError()); + Console.Warning("CreateMappingFromFile failed!"); } - m_mappings[slot] = reinterpret_cast(MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0)); + m_mappings[slot] = static_cast(HostSys::MapMapping(m_mapping_handles[slot], PageAccess_ReadWrite())); if (!m_mappings[slot]) { - Console.Warning("MapViewOfFile failed: %d", GetLastError()); + Console.Warning("MapSharedMemory failed!"); } -#endif } } } @@ -386,13 +379,13 @@ void FileMemoryCard::Close() m_filenames[slot] = {}; m_fileSize[slot] = -1; -#ifdef _WIN32 + if (m_mappings[slot]) { - UnmapViewOfFile(m_mappings[slot]); + HostSys::UnmapSharedMemory(m_mappings[slot], 0); + HostSys::DestroyMapping(m_mapping_handles[slot]); m_mappings[slot] = nullptr; } -#endif } } From cbc55d45d1fe2b9849a3434e9178bc4b01856436 Mon Sep 17 00:00:00 2001 From: Ty Lamontagne Date: Mon, 13 Jan 2025 21:46:29 -0500 Subject: [PATCH 3/3] Memory Cards: Actually implement the optimizations and the bugs that come with it on non-windows platforms --- common/Darwin/DarwinMisc.cpp | 4 +- common/HostSys.h | 2 +- common/Linux/LnxHostSys.cpp | 4 +- common/Windows/WinHostSys.cpp | 2 +- pcsx2/SIO/Memcard/MemoryCardFile.cpp | 99 +++++++++++----------------- 5 files changed, 44 insertions(+), 67 deletions(-) diff --git a/common/Darwin/DarwinMisc.cpp b/common/Darwin/DarwinMisc.cpp index 73c835f67d..2432d63dd3 100644 --- a/common/Darwin/DarwinMisc.cpp +++ b/common/Darwin/DarwinMisc.cpp @@ -296,11 +296,11 @@ void* HostSys::CreateMappingFromFile(FILE* file) return reinterpret_cast(static_cast(fileno(file))); } -void* HostSys::MapMapping(void* handle, const PageProtectionMode& mode) +void* HostSys::MapMapping(void* handle, size_t size, const PageProtectionMode& mode) { const u32 mmap_prot = (mode.CanWrite() ? (PROT_READ | PROT_WRITE) : (PROT_READ)) | (mode.CanExecute() ? PROT_EXEC : 0); - return mmap(nullptr, 0, mmap_prot, MAP_PRIVATE | MAP_ANON, static_cast(reinterpret_cast(handle)), 0); + return mmap(nullptr, size, mmap_prot, MAP_PRIVATE | MAP_ANON, static_cast(reinterpret_cast(handle)), 0); } void HostSys::DestroyMapping(void* handle) diff --git a/common/HostSys.h b/common/HostSys.h index 68d9893a11..36561c8397 100644 --- a/common/HostSys.h +++ b/common/HostSys.h @@ -103,7 +103,7 @@ namespace HostSys extern void* CreateSharedMemory(const char* name, size_t size); extern void* CreateMappingFromFile(FILE* file); - extern void* MapMapping(void* handle, const PageProtectionMode& mode); + extern void* MapMapping(void* handle, size_t size, const PageProtectionMode& mode); extern void DestroyMapping(void* handle); extern void DestroySharedMemory(void* ptr); diff --git a/common/Linux/LnxHostSys.cpp b/common/Linux/LnxHostSys.cpp index c28b6fe1f3..59fb7be38b 100644 --- a/common/Linux/LnxHostSys.cpp +++ b/common/Linux/LnxHostSys.cpp @@ -120,9 +120,9 @@ void* HostSys::CreateMappingFromFile(FILE* file) return reinterpret_cast(static_cast(fileno(file))); } -void* HostSys::MapMapping(void* handle, const PageProtectionMode& mode) +void* HostSys::MapMapping(void* handle, size_t size, const PageProtectionMode& mode) { - return HostSys::MapSharedMemory(handle, 0, nullptr, 0, mode); + return HostSys::MapSharedMemory(handle, 0, nullptr, size, mode); } void HostSys::DestroyMapping(void* handle) diff --git a/common/Windows/WinHostSys.cpp b/common/Windows/WinHostSys.cpp index 2fe1479d87..c51579bbe1 100644 --- a/common/Windows/WinHostSys.cpp +++ b/common/Windows/WinHostSys.cpp @@ -79,7 +79,7 @@ void* HostSys::CreateMappingFromFile(FILE* fd) 0, 0, nullptr)); } -void* HostSys::MapMapping(void* handle, const PageProtectionMode& mode) +void* HostSys::MapMapping(void* handle, size_t size, const PageProtectionMode& mode) { return MapViewOfFile(static_cast(handle), FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0); } diff --git a/pcsx2/SIO/Memcard/MemoryCardFile.cpp b/pcsx2/SIO/Memcard/MemoryCardFile.cpp index ebde768cb3..dc35354b0e 100644 --- a/pcsx2/SIO/Memcard/MemoryCardFile.cpp +++ b/pcsx2/SIO/Memcard/MemoryCardFile.cpp @@ -168,6 +168,8 @@ protected: bool m_ispsx[8] = {}; u32 m_chkaddr = 0; + std::chrono::time_point m_lastSaveTime = std::chrono::system_clock::now(); + public: FileMemoryCard(); ~FileMemoryCard(); @@ -323,10 +325,22 @@ void FileMemoryCard::Open() "Close any other instances of PCSX2, or restart your computer.\n"), fname)); } - else // Load checksum + else // Load memory map and checksum { m_fileSize[slot] = FileSystem::FSize64(m_file[slot]); + m_mapping_handles[slot] = HostSys::CreateMappingFromFile(m_file[slot]); + if (!m_mapping_handles[slot]) + { + Console.Warning("CreateMappingFromFile failed!"); + } + + m_mappings[slot] = static_cast(HostSys::MapMapping(m_mapping_handles[slot], m_fileSize[slot], PageAccess_ReadWrite())); + if (!m_mappings[slot]) + { + Console.Warning("MapSharedMemory failed! %d. %s", errno, strerror(errno)); + } + Console.WriteLnFmt(Color_Green, "McdSlot {} [File]: {} [{} MB, {}]", slot, Path::GetFileName(fname), (m_fileSize[slot] + (MCD_SIZE + 1)) / MC2_MBSIZE, FileMcd_IsMemoryCardFormatted(m_file[slot]) ? "Formatted" : "UNFORMATTED"); @@ -335,22 +349,9 @@ void FileMemoryCard::Open() m_ispsx[slot] = m_fileSize[slot] == 0x20000; m_chkaddr = 0x210; - if (!m_ispsx[slot] && FileSystem::FSeek64(m_file[slot], m_chkaddr, SEEK_SET) == 0) + if (!m_ispsx[slot]) { - const size_t read_result = std::fread(&m_chksum[slot], sizeof(m_chksum[slot]), 1, m_file[slot]); - if (read_result == 0) - Host::ReportErrorAsync("Memory Card Read Failed", "Error reading memory card."); - } - - m_mapping_handles[slot] = HostSys::CreateMappingFromFile(m_file[slot]); - if (!m_mapping_handles[slot]) - { - Console.Warning("CreateMappingFromFile failed!"); - } - m_mappings[slot] = static_cast(HostSys::MapMapping(m_mapping_handles[slot], PageAccess_ReadWrite())); - if (!m_mappings[slot]) - { - Console.Warning("MapSharedMemory failed!"); + std::memcpy(&m_chksum[slot], m_mappings[slot] + m_chkaddr, sizeof(m_chksum[slot])); } } } @@ -364,8 +365,8 @@ void FileMemoryCard::Close() 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]); + if (!m_ispsx[slot]) + std::memcpy(m_mappings[slot] + m_chkaddr, &m_chksum[slot], sizeof(m_chksum[slot])); std::fclose(m_file[slot]); m_file[slot] = nullptr; @@ -377,15 +378,15 @@ void FileMemoryCard::Close() FileSystem::DeleteFilePath(name_in.c_str()); } - m_filenames[slot] = {}; - m_fileSize[slot] = -1; - if (m_mappings[slot]) { - HostSys::UnmapSharedMemory(m_mappings[slot], 0); + HostSys::UnmapSharedMemory(m_mappings[slot], m_fileSize[slot]); HostSys::DestroyMapping(m_mapping_handles[slot]); m_mappings[slot] = nullptr; } + + m_filenames[slot] = {}; + m_fileSize[slot] = -1; } } @@ -453,7 +454,6 @@ s32 FileMemoryCard::Read(uint slot, u8* dest, u32 adr, int size) return 1; } -#ifdef _WIN32 if (adr + size > static_cast(m_fileSize[slot])) { Console.Warning("(FileMcd) Warning: read past end of file. (%d) [%08X]", slot, adr); @@ -461,15 +461,13 @@ s32 FileMemoryCard::Read(uint slot, u8* dest, u32 adr, int size) std::memcpy(dest, m_mappings[slot] + adr, size); return 1; -#else - if (!Seek(mcfp, adr)) - return 0; - return std::fread(dest, size, 1, mcfp) == 1; -#endif } s32 FileMemoryCard::Save(uint slot, const u8* src, u32 adr, int size) { + if (adr + size > static_cast(m_fileSize[slot])) + return 0; + std::FILE* mcfp = m_file[slot]; if (!mcfp) @@ -487,14 +485,10 @@ s32 FileMemoryCard::Save(uint slot, const u8* src, u32 adr, int size) } else { - if (!Seek(mcfp, adr)) - return 0; if (static_cast(m_currentdata.size()) < size) m_currentdata.resize(size); - const size_t read_result = std::fread(m_currentdata.data(), size, 1, mcfp); - if (read_result == 0) - Host::ReportErrorAsync("Memory Card Read Failed", "Error reading memory card."); + std::memcpy(m_currentdata.data(), m_mappings[slot] + adr, size); for (int i = 0; i < size; i++) { @@ -516,26 +510,18 @@ s32 FileMemoryCard::Save(uint slot, const u8* src, u32 adr, int size) } } - if (!Seek(mcfp, adr)) - return 0; + std::memcpy(m_mappings[slot] + adr, src, size); - if (std::fwrite(m_currentdata.data(), size, 1, mcfp) == 1) + std::chrono::duration elapsed = std::chrono::system_clock::now() - m_lastSaveTime; + if (elapsed > std::chrono::seconds(5)) { - static auto last = std::chrono::time_point(); - - std::chrono::duration elapsed = std::chrono::system_clock::now() - last; - if (elapsed > std::chrono::seconds(5)) - { - Host::AddIconOSDMessage(fmt::format("MemoryCardSave{}", slot), ICON_PF_MEMORY_CARD, - fmt::format(TRANSLATE_FS("MemoryCard", "Memory Card '{}' was saved to storage."), - Path::GetFileName(m_filenames[slot])), - Host::OSD_INFO_DURATION); - last = std::chrono::system_clock::now(); - } - return 1; + Host::AddIconOSDMessage(fmt::format("MemoryCardSave{}", slot), ICON_PF_MEMORY_CARD, + fmt::format(TRANSLATE_FS("MemoryCard", "Memory Card '{}' was saved to storage."), + Path::GetFileName(m_filenames[slot])), + Host::OSD_INFO_DURATION); + m_lastSaveTime = std::chrono::system_clock::now(); } - - return 0; + return 1; } s32 FileMemoryCard::EraseBlock(uint slot, u32 adr) @@ -549,15 +535,10 @@ s32 FileMemoryCard::EraseBlock(uint slot, u32 adr) if (!Seek(mcfp, adr)) return 0; -#ifdef _WIN32 + std::memset(m_mappings[slot] + adr, 0xff, MC2_ERASE_SIZE); return 1; -#else - std::array buffer; - std::memset(buffer.data(), 0xff, buffer.size()); - return std::fwrite(buffer.data(), buffer.size(), 1, mcfp) == 1; -#endif } u64 FileMemoryCard::GetCRC(uint slot) @@ -570,9 +551,6 @@ u64 FileMemoryCard::GetCRC(uint slot) if (m_ispsx[slot]) { - if (!Seek(mcfp, 0)) - return 0; - const s64 mcfpsize = m_fileSize[slot]; if (mcfpsize < 0) return 0; @@ -584,8 +562,7 @@ u64 FileMemoryCard::GetCRC(uint slot) const uint filesize = static_cast(mcfpsize) / sizeof(buffer); for (uint i = filesize; i; --i) { - if (std::fread(buffer, sizeof(buffer), 1, mcfp) != 1) - return 0; + std::memcpy(buffer, m_mappings[slot], sizeof(buffer)); for (uint t = 0; t < std::size(buffer); ++t) retval ^= buffer[t];