diff --git a/common/Darwin/DarwinMisc.cpp b/common/Darwin/DarwinMisc.cpp index 9bc7138855..ab4dd45dae 100644 --- a/common/Darwin/DarwinMisc.cpp +++ b/common/Darwin/DarwinMisc.cpp @@ -318,6 +318,29 @@ size_t HostSys::GetRuntimeCacheLineSize() return static_cast(std::max(sysctlbyname_T("hw.cachelinesize").value_or(0), 0)); } +void* HostSys::CreateMappingFromFile(FILE* file) +{ + return reinterpret_cast(static_cast(fileno(file))); +} + +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, size, mmap_prot, MAP_PRIVATE, 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::FlushMapping(void* handle, [[maybe_unused]] void* baseAddr, size_t size) +{ + msync(handle, size, MS_SYNC); +} + #ifdef ARCH_ARM64 static thread_local int s_code_write_depth = 0; diff --git a/common/FileSystem.cpp b/common/FileSystem.cpp index 24eddeb3b8..9c911875b9 100644 --- a/common/FileSystem.cpp +++ b/common/FileSystem.cpp @@ -1142,6 +1142,23 @@ s64 FileSystem::FSize64(std::FILE* fp) return -1; } +bool FileSystem::FFlush(std::FILE *fp) +{ +#ifdef _WIN32 + HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(fp)); + if(hFile != INVALID_HANDLE_VALUE) + { + if(FlushFileBuffers(hFile)) + { + return true; + } + } + return false; +#else + return !std::fflush(fp); +#endif +} + s64 FileSystem::GetPathFileSize(const char* Path) { FILESYSTEM_STAT_DATA sd; diff --git a/common/FileSystem.h b/common/FileSystem.h index 7003135e53..8cdd5b762c 100644 --- a/common/FileSystem.h +++ b/common/FileSystem.h @@ -121,6 +121,8 @@ namespace FileSystem s64 FTell64(std::FILE* fp); s64 FSize64(std::FILE* fp); + bool FFlush(std::FILE* fp); + int OpenFDFile(const char* filename, int flags, int mode, Error* error = nullptr); /// Sharing modes for OpenSharedCFile(). diff --git a/common/HostSys.h b/common/HostSys.h index 3d6ce3b542..0bbcbf85fe 100644 --- a/common/HostSys.h +++ b/common/HostSys.h @@ -95,6 +95,12 @@ 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, size_t size, const PageProtectionMode& mode); + extern void DestroyMapping(void* handle); + extern void FlushMapping(void* handle, void* baseAddr, size_t size); + extern void DestroySharedMemory(void* ptr); /// JIT write protect for Apple Silicon. Needs to be called prior to writing to any RWX pages. diff --git a/common/Linux/LnxHostSys.cpp b/common/Linux/LnxHostSys.cpp index 523e6eaaaf..afc64e5fa3 100644 --- a/common/Linux/LnxHostSys.cpp +++ b/common/Linux/LnxHostSys.cpp @@ -83,6 +83,27 @@ 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, size_t size, const PageProtectionMode& mode) +{ + return HostSys::MapSharedMemory(handle, 0, nullptr, size, mode); +} + +void HostSys::DestroyMapping(void* handle) +{ + // The handle mmap requires is the same as the file descriptor. + return; +} + +void HostSys::FlushMapping(void* handle, [[maybe_unused]] void* baseAddr, size_t size) +{ + msync(handle, size, MS_SYNC); +} + void HostSys::DestroySharedMemory(void* ptr) { close(static_cast(reinterpret_cast(ptr))); diff --git a/common/Windows/WinHostSys.cpp b/common/Windows/WinHostSys.cpp index 90c23051b2..0012d0ef17 100644 --- a/common/Windows/WinHostSys.cpp +++ b/common/Windows/WinHostSys.cpp @@ -12,6 +12,7 @@ #include "fmt/format.h" +#include #include static DWORD ConvertToWinApi(const PageProtectionMode& mode) @@ -55,6 +56,27 @@ 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, size_t size, 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::FlushMapping([[maybe_unused]] void* handle, void* baseAddr, size_t size) +{ + FlushViewOfFile(baseAddr, size); +} + void HostSys::DestroySharedMemory(void* ptr) { CloseHandle(static_cast(ptr)); diff --git a/pcsx2/SIO/Memcard/MemoryCardFile.cpp b/pcsx2/SIO/Memcard/MemoryCardFile.cpp index 5cae5578a4..13001bfbec 100644 --- a/pcsx2/SIO/Memcard/MemoryCardFile.cpp +++ b/pcsx2/SIO/Memcard/MemoryCardFile.cpp @@ -11,11 +11,14 @@ #include "common/Console.h" #include "common/Error.h" #include "common/FileSystem.h" +#include "common/HostSys.h" #include "common/Path.h" #include "common/StringUtil.h" +#include "common/Threading.h" #include #include +#include #include "Config.h" #include "Host.h" @@ -24,6 +27,7 @@ #include "fmt/format.h" +#include #include static constexpr int MCD_SIZE = 1024 * 8 * 16; // Legacy PSX card default size @@ -160,6 +164,14 @@ class FileMemoryCard { protected: std::FILE* m_file[8] = {}; + u8* m_mappings[8] = {}; + void* m_mapping_handles[8] = {}; + + std::thread flushThread; + std::atomic stopFlushThread{false}; + std::mutex flushMutex; + std::condition_variable flushCV; + s64 m_fileSize[8] = {}; std::string m_filenames[8] = {}; std::vector m_currentdata; @@ -167,6 +179,8 @@ protected: bool m_ispsx[8] = {}; u32 m_chkaddr = 0; + std::chrono::time_point m_lastSaveTime = std::chrono::system_clock::now(); + public: FileMemoryCard(); ~FileMemoryCard(); @@ -188,6 +202,9 @@ public: protected: bool Seek(std::FILE* f, u32 adr); bool Create(const char* mcdFile, uint sizeInMB); + + void FlushAllCards(); + void FlushThreadFunc(); }; uint FileMcd_GetMtapPort(uint slot) @@ -256,9 +273,20 @@ FileMemoryCard::FileMemoryCard() { m_fileSize[slot] = -1; } + + flushThread = std::thread(&FileMemoryCard::FlushThreadFunc, this); } -FileMemoryCard::~FileMemoryCard() = default; +FileMemoryCard::~FileMemoryCard() +{ + stopFlushThread = true; + flushCV.notify_all(); + if(flushThread.joinable()) + { + Console.WriteLn("MemoryCards: Waiting on memory card flushing thread."); + flushThread.join(); + } +} void FileMemoryCard::Open() { @@ -322,10 +350,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"); @@ -334,11 +374,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."); + std::memcpy(&m_chksum[slot], m_mappings[slot] + m_chkaddr, sizeof(m_chksum[slot])); } } } @@ -346,14 +384,15 @@ void FileMemoryCard::Open() void FileMemoryCard::Close() { + std::unique_lock lock(flushMutex); 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]); + 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; @@ -365,6 +404,13 @@ void FileMemoryCard::Close() FileSystem::DeleteFilePath(name_in.c_str()); } + if (m_mappings[slot]) + { + 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; } @@ -433,13 +479,21 @@ s32 FileMemoryCard::Read(uint slot, u8* dest, u32 adr, int size) memset(dest, 0, size); return 1; } - if (!Seek(mcfp, adr)) - return 0; - return std::fread(dest, size, 1, mcfp) == 1; + + 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; } 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) @@ -457,14 +511,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++) { @@ -486,26 +536,20 @@ 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; + flushCV.notify_all(); + return 1; } s32 FileMemoryCard::EraseBlock(uint slot, u32 adr) @@ -520,9 +564,9 @@ s32 FileMemoryCard::EraseBlock(uint slot, u32 adr) if (!Seek(mcfp, adr)) return 0; - u8 buf[MC2_ERASE_SIZE]; - std::memset(buf, 0xff, sizeof(buf)); - return std::fwrite(buf, sizeof(buf), 1, mcfp) == 1; + std::memset(m_mappings[slot] + adr, 0xff, MC2_ERASE_SIZE); + + return 1; } u64 FileMemoryCard::GetCRC(uint slot) @@ -535,9 +579,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; @@ -549,8 +590,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]; @@ -564,6 +604,45 @@ u64 FileMemoryCard::GetCRC(uint slot) return retval; } +void FileMemoryCard::FlushAllCards() +{ + // The lock in FlushThreadFunc should keep us safe from the main thread touching the handles here + for(size_t i = 0; i < 8; i++) + { + if(m_fileSize[i] > 0) + { + HostSys::FlushMapping(m_mapping_handles[i], m_mappings[i], m_fileSize[i]); + FileSystem::FFlush(m_file[i]); + } + } +} + +void FileMemoryCard::FlushThreadFunc() { + Threading::SetNameOfCurrentThread("MemoryCard I/O Flush Thread"); + while (!stopFlushThread) { + std::unique_lock lock(flushMutex); + flushCV.wait(lock, [this] { + return stopFlushThread || MemcardBusy::IsBusy(); + }); + Console.Warning("!! Memory card is busy, waiting to commit to disk!"); + while (!stopFlushThread) { + if (!MemcardBusy::IsBusy()) { + FlushAllCards(); + Console.Warning("!! Memory card committed to disk!"); + break; + } + + lock.unlock(); + Threading::Sleep(50); + lock.lock(); + } + } + // In the event that the user closes PCSX2 while the memory card is busy (against our recommendations!!) + // we can at least ensure that the memory card is flushed to disk. + FlushAllCards(); +} + + // -------------------------------------------------------------------------------------- // MemoryCard Component API Bindings // -------------------------------------------------------------------------------------- @@ -610,7 +689,6 @@ void FileMcd_EmuOpen() return; FileMcd_Open = true; - Mcd::impl.Open(); Mcd::implFolder.SetFiltering(true); Mcd::implFolder.Open(); diff --git a/pcsx2/SIO/Memcard/MemoryCardProtocol.cpp b/pcsx2/SIO/Memcard/MemoryCardProtocol.cpp index 245b584a25..61c9289e57 100644 --- a/pcsx2/SIO/Memcard/MemoryCardProtocol.cpp +++ b/pcsx2/SIO/Memcard/MemoryCardProtocol.cpp @@ -207,6 +207,7 @@ void MemoryCardProtocol::GetTerminator() void MemoryCardProtocol::WriteData() { + MemcardBusy::SetBusy(); MC_LOG.WriteLn("%s", __FUNCTION__); PS1_FAIL(); g_Sio2FifoOut.push_back(0x00); @@ -230,8 +231,6 @@ void MemoryCardProtocol::WriteData() g_Sio2FifoOut.push_back(mcd->term); ReadWriteIncrement(writeLength); - - MemcardBusy::SetBusy(); } void MemoryCardProtocol::ReadData() @@ -332,6 +331,8 @@ u8 MemoryCardProtocol::PS1State(u8 data) u8 MemoryCardProtocol::PS1Write(u8 data) { + MemcardBusy::SetBusy(); + MC_LOG.WriteLn("%s", __FUNCTION__); bool sendAck = true; u8 ret = 0; @@ -396,7 +397,6 @@ u8 MemoryCardProtocol::PS1Write(u8 data) g_Sio0.SetAcknowledge(sendAck); ps1McState.currentByte++; - MemcardBusy::SetBusy(); return ret; } @@ -416,12 +416,12 @@ void MemoryCardProtocol::ReadWriteEnd() void MemoryCardProtocol::EraseBlock() { + MemcardBusy::SetBusy(); + MC_LOG.WriteLn("%s", __FUNCTION__); PS1_FAIL(); mcd->EraseBlock(); The2bTerminator(4); - - MemcardBusy::SetBusy(); } void MemoryCardProtocol::UnknownBoot()