diff --git a/pcsx2/DEV9/ATA/HddCreate.cpp b/pcsx2/DEV9/ATA/HddCreate.cpp index 62eaea78b7..bb23d31d40 100644 --- a/pcsx2/DEV9/ATA/HddCreate.cpp +++ b/pcsx2/DEV9/ATA/HddCreate.cpp @@ -20,17 +20,29 @@ #include #include "HddCreate.h" +#if _WIN32 +#include "common/RedtapeWindows.h" +#include "common/StringUtil.h" +#include +#include +#elif defined(__POSIX__) +#include +#include +#include +#include +#endif + void HddCreate::Start() { Init(); - WriteImage(filePath, neededSize); + WriteImage(filePath, neededSize, 1024); Cleanup(); } -void HddCreate::WriteImage(std::string hddPath, u64 reqSizeBytes) +void HddCreate::WriteImage(std::string hddPath, u64 fileBytes, u64 zeroSizeBytes) { constexpr int buffsize = 4 * 1024; - u8 buff[buffsize] = {0}; //4kb + u8 buff[buffsize] = {0}; // 4kb. if (FileSystem::FileExists(hddPath.c_str())) { @@ -47,35 +59,154 @@ void HddCreate::WriteImage(std::string hddPath, u64 reqSizeBytes) return; } - //Size file - const char zero = 0; - bool success = FileSystem::FSeek64(newImage.get(), reqSizeBytes - 1, SEEK_SET) == 0; - success = success && std::fwrite(&zero, 1, 1, newImage.get()) == 1; - success = success && FileSystem::FSeek64(newImage.get(), 0, SEEK_SET) == 0; + bool sparseSupported = false; +#ifdef _WIN32 + // Handle owned by CFile. + HANDLE nativeFile = reinterpret_cast(_get_osfhandle(_fileno(newImage.get()))); - if (!success) + if (nativeFile == INVALID_HANDLE_VALUE) { + Console.Error("DEV9: HddCreate: failed to get handle"); newImage.reset(); - FileSystem::DeleteFilePath(filePath.c_str()); + FileSystem::DeleteFilePath(hddPath.c_str()); errored.store(true); SetError(); return; } + // Check if we support sparse files. + DWORD dwFlags; + if (GetVolumeInformationByHandleW(nativeFile, nullptr, 0, nullptr, nullptr, &dwFlags, nullptr, 0) == FALSE) + { + Console.Error("DEV9: HddCreate: failed to check sparse"); + newImage.reset(); + FileSystem::DeleteFilePath(hddPath.c_str()); + errored.store(true); + SetError(); + return; + } + + if (dwFlags & FILE_SUPPORTS_SPARSE_FILES) + { + // Sparse files supported. + FILE_SET_SPARSE_BUFFER sparseSetting; + sparseSetting.SetSparse = true; + FILE_ZERO_DATA_INFORMATION sparseRange; + sparseRange.FileOffset.QuadPart = 0; + sparseRange.BeyondFinalZero.QuadPart = fileBytes; + DWORD dwTemp; + + if ((DeviceIoControl(nativeFile, FSCTL_SET_SPARSE, &sparseSetting, sizeof(sparseSetting), nullptr, 0, &dwTemp, nullptr) == FALSE) || + (DeviceIoControl(nativeFile, FSCTL_SET_ZERO_DATA, &sparseRange, sizeof(sparseRange), nullptr, 0, &dwTemp, nullptr) == FALSE)) + { + Console.Error("DEV9: HddCreate: Failed to set sparse"); + newImage.reset(); + FileSystem::DeleteFilePath(hddPath.c_str()); + errored.store(true); + SetError(); + return; + } + + sparseSupported = true; + } + + // Set filesize. + LARGE_INTEGER seekStart; + seekStart.QuadPart = 0; + LARGE_INTEGER seekEnd; + seekEnd.QuadPart = fileBytes; + + if ((SetFilePointerEx(nativeFile, seekEnd, nullptr, FILE_BEGIN) == FALSE) || + (SetEndOfFile(nativeFile) == FALSE) || + (SetFilePointerEx(nativeFile, seekStart, nullptr, FILE_BEGIN) == FALSE)) + { + Console.Error("DEV9: HddCreate: Failed to set size"); + newImage.reset(); + FileSystem::DeleteFilePath(hddPath.c_str()); + errored.store(true); + SetError(); + return; + } + +#elif defined(__POSIX__) + // Handle owned by CFile. + int nativeFile = fileno(newImage.get()); + + if (nativeFile == -1) + { + Console.Error("DEV9: HddCreate: failed to get handle"); + newImage.reset(); + FileSystem::DeleteFilePath(hddPath.c_str()); + errored.store(true); + SetError(); + return; + } + + // Set filesize. + if ((ftruncate(nativeFile, fileBytes) == -1) || + (lseek(nativeFile, 0, SEEK_SET) == -1)) + { + Console.Error("DEV9: HddCreate: Failed to set size"); + newImage.reset(); + FileSystem::DeleteFilePath(hddPath.c_str()); + errored.store(true); + SetError(); + return; + } + + // Check the blocks allocated to determine if file is spasre. + // Assume that we don't get a false positive from filesystems only supporting ValidDataLength. + struct stat fileInfo; + if (fstat(nativeFile, &fileInfo) == -1) + { + Console.Error("DEV9: HddCreate: Failed to check sparse"); + // Set filesize to zero to avoid potential freeze on close. + // Ignore any error, can't do much if this fails anyway. + [[maybe_unused]] int i = ftruncate(nativeFile, 0); + newImage.reset(); + FileSystem::DeleteFilePath(hddPath.c_str()); + errored.store(true); + SetError(); + return; + } + + if (fileInfo.st_blocks != static_cast(fileBytes / 512)) + { + // Sparse files supported. + sparseSupported = true; + // File is automatically sparse. + } +#endif + lastUpdate = std::chrono::steady_clock::now(); - //Round up - const s32 reqMiB = (reqSizeBytes + ((1024 * 1024) - 1)) / (1024 * 1024); - for (s32 iMiB = 0; iMiB < reqMiB; iMiB++) + // Round up. + const s32 reqMiB = (fileBytes + ((1024 * 1024) - 1)) / (1024 * 1024); + const s32 zeroMiB = (zeroSizeBytes + ((1024 * 1024) - 1)) / (1024 * 1024); + + s32 iMiB = 0; + if (sparseSupported) + iMiB = reqMiB - zeroMiB; + + for (; iMiB < reqMiB; iMiB++) { - //Round down - const s32 req4Kib = std::min(1024, (reqSizeBytes / 1024) - (u64)iMiB * 1024) / 4; + // Round down. + const s32 req4Kib = std::min(1024, (fileBytes / 1024) - (u64)iMiB * 1024) / 4; for (s32 i4kb = 0; i4kb < req4Kib; i4kb++) { if (std::fwrite(buff, buffsize, 1, newImage.get()) != 1) { + std::fflush(newImage.get()); + // Set filesize to zero to avoid potential freeze on close. +#ifdef _WIN32 + SetFilePointerEx(nativeFile, seekStart, nullptr, FILE_BEGIN); + SetEndOfFile(nativeFile); +#elif defined(__POSIX__) + // Ignore any error, can't do much if this fails anyway. + [[maybe_unused]] int i = ftruncate(nativeFile, 0); +#endif newImage.reset(); - FileSystem::DeleteFilePath(filePath.c_str()); + FileSystem::DeleteFilePath(hddPath.c_str()); errored.store(true); SetError(); return; @@ -84,11 +215,20 @@ void HddCreate::WriteImage(std::string hddPath, u64 reqSizeBytes) if (req4Kib != 256) { - const s32 remainingBytes = reqSizeBytes - (((u64)iMiB) * (1024 * 1024) + req4Kib * 4096); + const s32 remainingBytes = fileBytes - (((u64)iMiB) * (1024 * 1024) + req4Kib * 4096); if (std::fwrite(buff, remainingBytes, 1, newImage.get()) != 1) { + std::fflush(newImage.get()); + // Set filesize to zero to avoid potential freeze on close. +#ifdef _WIN32 + SetFilePointerEx(nativeFile, seekStart, nullptr, FILE_BEGIN); + SetEndOfFile(nativeFile); +#elif defined(__POSIX__) + // Ignore any error, can't do much if this fails anyway. + [[maybe_unused]] int i = ftruncate(nativeFile, 0); +#endif newImage.reset(); - FileSystem::DeleteFilePath(filePath.c_str()); + FileSystem::DeleteFilePath(hddPath.c_str()); errored.store(true); SetError(); return; @@ -103,8 +243,17 @@ void HddCreate::WriteImage(std::string hddPath, u64 reqSizeBytes) } if (canceled.load()) { + std::fflush(newImage.get()); + // Set filesize to zero to avoid potential freeze on close. +#ifdef _WIN32 + SetFilePointerEx(nativeFile, seekStart, nullptr, FILE_BEGIN); + SetEndOfFile(nativeFile); +#elif defined(__POSIX__) + // Ignore any error, can't do much if this fails anyway. + [[maybe_unused]] int i = ftruncate(nativeFile, 0); +#endif newImage.reset(); - FileSystem::DeleteFilePath(filePath.c_str()); + FileSystem::DeleteFilePath(hddPath.c_str()); errored.store(true); SetError(); return; diff --git a/pcsx2/DEV9/ATA/HddCreate.h b/pcsx2/DEV9/ATA/HddCreate.h index 0bd21fcbae..936247f5ee 100644 --- a/pcsx2/DEV9/ATA/HddCreate.h +++ b/pcsx2/DEV9/ATA/HddCreate.h @@ -49,5 +49,5 @@ protected: void SetCanceled(); private: - void WriteImage(std::string hddPath, u64 reqSizeBytes); + void WriteImage(std::string hddPath, u64 fileBytes, u64 zeroSizeBytes); };