file_util: Fix file behaviour on Windows (#1841)

This commit is contained in:
PabloMK7 2026-03-07 20:33:29 +01:00 committed by GitHub
parent ced1ec0112
commit 1e0df67cc4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 69 additions and 33 deletions

View File

@ -211,10 +211,33 @@ bool Delete(const std::string& filename) {
}
#ifdef _WIN32
if (!DeleteFileW(Common::UTF8ToUTF16W(filename).c_str())) {
LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
return false;
// On windows, if we delete a file with an open handle (pending to be deleted) and
// then try to open the same file name again, it will fail. On linux this doesn't happen
// as the new file will be a different inode, even if it has the same file name.
// The 3ds is linux-like, so to emulate this behaviour we need to rename the file
// first to an unique name then mark it for deletion. This way we can open new files
// with the same name. Once all handles of the old file are closed, the old file will be
// finally deleted.
static std::atomic<uint64_t> counter{0};
const std::wstring wfilename = Common::UTF8ToUTF16W(filename);
const DWORD pid = GetCurrentProcessId();
const uint64_t id = counter++;
std::wstring deleted_path =
wfilename + L".deleted." + std::to_wstring(pid) + L"." + std::to_wstring(id);
// Rename first
if (MoveFileExW(wfilename.c_str(), deleted_path.c_str(), MOVEFILE_REPLACE_EXISTING)) {
// Then mark file for deletion
DeleteFileW(deleted_path.c_str());
return true;
}
LOG_ERROR(Common_Filesystem, "Rename to deleted path failed on {}: {}", filename,
GetLastErrorMsg());
return false;
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
if (!AndroidStorage::DeleteDocument(filename)) {
LOG_ERROR(Common_Filesystem, "unlink failed on {}", filename);
@ -344,9 +367,10 @@ bool DeleteDir(const std::string& filename) {
bool Rename(const std::string& srcFullPath, const std::string& destFullPath) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFullPath, destFullPath);
#ifdef _WIN32
if (_wrename(Common::UTF8ToUTF16W(srcFullPath).c_str(),
Common::UTF8ToUTF16W(destFullPath).c_str()) == 0)
if (MoveFileExW(Common::UTF8ToUTF16W(srcFullPath).c_str(),
Common::UTF8ToUTF16W(destFullPath).c_str(), MOVEFILE_REPLACE_EXISTING)) {
return true;
}
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
// srcFullPath and destFullPath are relative to the user directory
if (AndroidStorage::GetBuildFlavor() == AndroidStorage::AndroidBuildFlavors::GOOGLEPLAY) {
@ -1167,11 +1191,46 @@ bool IOFile::Open() {
Close();
#ifdef _WIN32
if (flags == 0) {
flags = _SH_DENYNO;
// Open with FILE_SHARE_READ, FILE_SHARE_WRITE and FILE_SHARE_DELETE
// flags. This mimics linux behaviour as much as possible, which
// the 3DS also does.
const std::wstring wfilename = Common::UTF8ToUTF16W(filename);
DWORD access = 0;
DWORD creation = OPEN_EXISTING;
if (openmode.find("r") != std::string::npos)
access |= GENERIC_READ;
if (openmode.find("w") != std::string::npos) {
access |= GENERIC_WRITE;
creation = CREATE_ALWAYS;
}
m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
Common::UTF8ToUTF16W(openmode).c_str(), flags);
if (openmode.find("a") != std::string::npos) {
access |= FILE_APPEND_DATA;
creation = OPEN_ALWAYS;
}
if (openmode.find("+") != std::string::npos) {
access |= GENERIC_READ | GENERIC_WRITE;
}
HANDLE h = CreateFileW(wfilename.c_str(), access,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,
creation, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) {
m_good = false;
return false;
}
int fd = _open_osfhandle(reinterpret_cast<intptr_t>(h), 0);
if (fd == -1) {
CloseHandle(h);
m_good = false;
return m_good;
}
m_file = _fdopen(fd, openmode.c_str());
m_good = m_file != nullptr;
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)

View File

@ -469,7 +469,6 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
m_emu_window = &emu_window;
m_secondary_window = secondary_window;
m_filepath = filepath;
self_delete_pending = false;
// Reset counters and set time origin to current frame
[[maybe_unused]] const PerfStats::Results result = GetAndResetPerfStats();
@ -721,10 +720,6 @@ void System::Shutdown(bool is_deserializing) {
memory.reset();
if (self_delete_pending)
FileUtil::Delete(m_filepath);
self_delete_pending = false;
LOG_DEBUG(Core, "Shutdown OK");
}

View File

@ -366,15 +366,6 @@ public:
bool LoadStateBuffer(std::vector<u8> buffer);
/// Self delete ncch
bool SetSelfDelete(const std::string& file) {
if (m_filepath == file) {
self_delete_pending = true;
return true;
}
return false;
}
/// Applies any changes to settings to this core instance.
void ApplySettings();
@ -480,7 +471,6 @@ private:
std::string m_chainloadpath;
std::optional<u8> m_mem_mode;
u64 title_id;
bool self_delete_pending;
std::mutex signal_mutex;
Signal current_signal;

View File

@ -916,15 +916,7 @@ bool CIAFile::Close() {
if (abort) {
break;
}
// If the file to delete is the current launched rom, signal the system to delete
// the current rom instead of deleting it now, once all the handles to the file
// are closed.
std::string to_delete =
GetTitleContentPath(media_type, old_tmd.GetTitleID(), old_index);
if (!system.IsPoweredOn() || !system.SetSelfDelete(to_delete)) {
FileUtil::Delete(to_delete);
}
FileUtil::Delete(GetTitleContentPath(media_type, old_tmd.GetTitleID(), old_index));
}
FileUtil::Delete(old_tmd_path);