diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fa828ff82..bfd5e69035 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2276,6 +2276,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Util/AudioFormat.h Core/Util/GameManager.cpp Core/Util/GameManager.h + Core/Util/MemStick.cpp + Core/Util/MemStick.h Core/Util/GameDB.cpp Core/Util/GameDB.h Core/Util/PortManager.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 6a350b693c..c9acd32dc1 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -1079,6 +1079,7 @@ + @@ -1449,6 +1450,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 1b49e9273d..0918b045f4 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1303,6 +1303,9 @@ Util + + Util + @@ -2082,6 +2085,9 @@ Util + + Util + diff --git a/Core/Util/MemStick.cpp b/Core/Util/MemStick.cpp new file mode 100644 index 0000000000..aa27a2b2ee --- /dev/null +++ b/Core/Util/MemStick.cpp @@ -0,0 +1,205 @@ +#include "Common/File/Path.h" +#include "Common/File/FileUtil.h" +#include "Common/File/DirListing.h" +#include "Common/Log.h" +#include "Common/StringUtils.h" +#include "Common/Data/Text/I18n.h" +#include "Common/Data/Text/Parsers.h" + +#include "Core/Util/MemStick.h" +#include "Core/Config.h" +#include "Core/System.h" +#include "Core/Reporting.h" + +bool FolderSeemsToBeUsed(const Path &newMemstickFolder) { + // Inspect the potential new folder, quickly. + if (File::Exists(newMemstickFolder / "PSP/SAVEDATA") || File::Exists(newMemstickFolder / "SAVEDATA")) { + // Does seem likely. We could add more criteria like checking for actual savegames or something. + return true; + } else { + return false; + } +} + +bool SwitchMemstickFolderTo(Path newMemstickFolder) { + // Doesn't already exist, create. + // Should this ever happen? + if (newMemstickFolder.Type() == PathType::NATIVE) { + if (!File::Exists(newMemstickFolder)) { + File::CreateFullPath(newMemstickFolder); + } + Path testWriteFile = newMemstickFolder / ".write_verify_file"; + if (!File::WriteDataToFile(true, "1", 1, testWriteFile)) { + return false; + } + File::Delete(testWriteFile); + } else { + // TODO: Do the same but with scoped storage? Not really necessary, right? If it came from a browse + // for folder, we can assume it exists and is writable, barring wacky race conditions like the user + // being connected by USB and deleting it. + } + + Path memStickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; +#if PPSSPP_PLATFORM(UWP) + File::Delete(memStickDirFile); + if (newMemstickFolder != g_Config.internalDataDirectory) { +#endif + + std::string str = newMemstickFolder.ToString(); + if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) { + ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", newMemstickFolder.c_str(), memStickDirFile.c_str()); + // Not sure what to do if this file can't be written. Disk full? + } + +#if PPSSPP_PLATFORM(UWP) + } +#endif + + // Save so the settings, at least, are transferred. + g_Config.memStickDirectory = newMemstickFolder; + g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); + g_Config.UpdateIniLocation(); + return true; +} + + +// Keep the size with the file, so we can skip overly large ones in the move. +// The user will have to take care of them afterwards, it'll just take too long probably. +struct FileSuffix { + std::string suffix; + u64 fileSize; +}; + +static bool ListFileSuffixesRecursively(const Path &root, const Path &folder, std::vector &dirSuffixes, std::vector &fileSuffixes) { + std::vector files; + if (!File::GetFilesInDir(folder, &files)) { + return false; + } + + for (auto &file : files) { + if (file.isDirectory) { + std::string dirSuffix; + if (root.ComputePathTo(file.fullName, dirSuffix)) { + if (!dirSuffix.empty()) { + dirSuffixes.push_back(dirSuffix); + ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes); + } + } else { + ERROR_LOG_REPORT(SYSTEM, "Failed to compute PathTo from '%s' to '%s'", root.c_str(), folder.c_str()); + } + } else { + std::string fileSuffix; + if (root.ComputePathTo(file.fullName, fileSuffix)) { + if (!fileSuffix.empty()) { + fileSuffixes.push_back(FileSuffix{ fileSuffix, file.size }); + } + } + } + } + + return true; +} + +MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressReporter &progressReporter) { + auto ms = GetI18NCategory(I18NCat::MEMSTICK); + if (moveSrc.GetFilename() != "PSP") { + moveSrc = moveSrc / "PSP"; + } + if (moveDest.GetFilename() != "PSP") { + moveDest = moveDest / "PSP"; + File::CreateDir(moveDest); + } + + INFO_LOG(SYSTEM, "About to move PSP data from '%s' to '%s'", moveSrc.c_str(), moveDest.c_str()); + + // Search through recursively, listing the files to move and also summing their sizes. + std::vector fileSuffixesToMove; + std::vector directorySuffixesToCreate; + + // NOTE: It's correct to pass moveSrc twice here, it's to keep the root in the recursion. + if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove)) { + // TODO: Handle failure listing files. + std::string error = "Failed to read old directory"; + INFO_LOG(SYSTEM, "%s", error.c_str()); + progressReporter.Set(ms->T(error.c_str())); + return new MoveResult{ false, error }; + } + + bool dryRun = false; // Useful for debugging. + + size_t failedFiles = 0; + size_t skippedFiles = 0; + + // We're not moving huge files like ISOs during this process, unless + // they can be directly moved, without rewriting the file. + const uint64_t BIG_FILE_THRESHOLD = 24 * 1024 * 1024; + + if (!moveSrc.empty()) { + // Better not interrupt the app while this is happening! + + // Create all the necessary directories. + for (auto &dirSuffix : directorySuffixesToCreate) { + Path dir = moveDest / dirSuffix; + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str()); + } else { + INFO_LOG(SYSTEM, "Creating dir '%s'", dir.c_str()); + progressReporter.Set(dirSuffix); + // Just ignore already-exists errors. + File::CreateDir(dir); + } + } + + for (auto &fileSuffix : fileSuffixesToMove) { + progressReporter.Set(StringFromFormat("%s (%s)", fileSuffix.suffix.c_str(), NiceSizeFormat(fileSuffix.fileSize).c_str())); + + Path from = moveSrc / fileSuffix.suffix; + Path to = moveDest / fileSuffix.suffix; + + if (fileSuffix.fileSize > BIG_FILE_THRESHOLD) { + // We only move big files if it's fast to do so. + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s' (%d bytes) if fast", from.c_str(), to.c_str(), (int)fileSuffix.fileSize); + } else { + if (!File::MoveIfFast(from, to)) { + INFO_LOG(SYSTEM, "Skipped moving file '%s' to '%s' (%s)", from.c_str(), to.c_str(), NiceSizeFormat(fileSuffix.fileSize).c_str()); + skippedFiles++; + } else { + INFO_LOG(SYSTEM, "Moved file '%s' to '%s'", from.c_str(), to.c_str()); + } + } + } else { + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s' (%d bytes)", from.c_str(), to.c_str(), (int)fileSuffix.fileSize); + } else { + // Remove the "from" prefix from the path. + // We have to drop down to string operations for this. + if (!File::Move(from, to)) { + ERROR_LOG(SYSTEM, "Failed to move file '%s' to '%s'", from.c_str(), to.c_str()); + failedFiles++; + // Should probably just bail? + } else { + INFO_LOG(SYSTEM, "Moved file '%s' to '%s'", from.c_str(), to.c_str()); + } + } + } + } + + // Delete all the old, now hopefully empty, directories. + // Hopefully DeleteDir actually fails if it contains a file... + for (auto &dirSuffix : directorySuffixesToCreate) { + Path dir = moveSrc / dirSuffix; + if (dryRun) { + INFO_LOG(SYSTEM, "dry run: Would have deleted dir '%s'", dir.c_str()); + } else { + INFO_LOG(SYSTEM, "Deleting dir '%s'", dir.c_str()); + progressReporter.Set(dirSuffix); + if (File::Exists(dir)) { + File::DeleteDir(dir); + } + } + } + } + + return new MoveResult{ true, "", failedFiles }; +} diff --git a/Core/Util/MemStick.h b/Core/Util/MemStick.h new file mode 100644 index 0000000000..6fdb868768 --- /dev/null +++ b/Core/Util/MemStick.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Common/File/Path.h" + +#include + +// Utility functions moved out from MemstickScreen. + +class MoveProgressReporter { +public: + void Set(const std::string &value) { + std::lock_guard guard(mutex_); + progress_ = value; + } + + std::string Get() { + std::lock_guard guard(mutex_); + return progress_; + } + +private: + std::string progress_; + std::mutex mutex_; +}; + +struct MoveResult { + bool success; // Got through the whole move. + std::string errorMessage; + size_t failedFiles; + size_t skippedFiles; +}; + +bool FolderSeemsToBeUsed(const Path &newMemstickFolder); +bool SwitchMemstickFolderTo(Path newMemstickFolder); +MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressReporter &progressReporter); diff --git a/GPU/Common/TextureReplacer.cpp b/GPU/Common/TextureReplacer.cpp index b0a3709d5b..d85e837221 100644 --- a/GPU/Common/TextureReplacer.cpp +++ b/GPU/Common/TextureReplacer.cpp @@ -735,6 +735,7 @@ bool TextureReplacer::WillSave(const ReplacedTextureDecodeInfo &replacedInfo) { void TextureReplacer::NotifyTextureDecoded(ReplacedTexture *texture, const ReplacedTextureDecodeInfo &replacedInfo, const void *data, int pitch, int level, int origW, int origH, int scaledW, int scaledH) { _assert_msg_(enabled_, "Replacement not enabled"); + _assert_(pitch >= 0); if (!WillSave(replacedInfo)) { // Ignore. @@ -798,7 +799,7 @@ void TextureReplacer::NotifyTextureDecoded(ReplacedTexture *texture, const Repla // while we're at it. saveBuf.resize(w * h * 4); for (int y = 0; y < h; y++) { - memcpy((u8 *)saveBuf.data() + y * w * 4, (const u8 *)data + y * pitch, w * sizeof(u32)); + memcpy((u8 *)saveBuf.data() + y * w * 4, (const u8 *)data + y * pitch, w * 4); } pitch = w * 4; diff --git a/UI/MemStickScreen.cpp b/UI/MemStickScreen.cpp index 5e9f0d02bc..8e54b8d4ca 100644 --- a/UI/MemStickScreen.cpp +++ b/UI/MemStickScreen.cpp @@ -44,64 +44,13 @@ #include "Core/Reporting.h" #include "Core/System.h" #include "Core/Util/GameManager.h" +#include "Core/Util/MemStick.h" #include "UI/MemStickScreen.h" #include "UI/MainScreen.h" #include "UI/MiscScreens.h" #include "UI/OnScreenDisplay.h" -static bool FolderSeemsToBeUsed(const Path &newMemstickFolder) { - // Inspect the potential new folder, quickly. - if (File::Exists(newMemstickFolder / "PSP/SAVEDATA") || File::Exists(newMemstickFolder / "SAVEDATA")) { - // Does seem likely. We could add more criteria like checking for actual savegames or something. - return true; - } else { - return false; - } -} - -static bool SwitchMemstickFolderTo(Path newMemstickFolder) { - // Doesn't already exist, create. - // Should this ever happen? - if (newMemstickFolder.Type() == PathType::NATIVE) { - if (!File::Exists(newMemstickFolder)) { - File::CreateFullPath(newMemstickFolder); - } - Path testWriteFile = newMemstickFolder / ".write_verify_file"; - if (!File::WriteDataToFile(true, "1", 1, testWriteFile)) { - return false; - } - File::Delete(testWriteFile); - } else { - // TODO: Do the same but with scoped storage? Not really necessary, right? If it came from a browse - // for folder, we can assume it exists and is writable, barring wacky race conditions like the user - // being connected by USB and deleting it. - } - - Path memStickDirFile = g_Config.internalDataDirectory / "memstick_dir.txt"; -#if PPSSPP_PLATFORM(UWP) - File::Delete(memStickDirFile); - if (newMemstickFolder != g_Config.internalDataDirectory) { -#endif - - std::string str = newMemstickFolder.ToString(); - if (!File::WriteDataToFile(true, str.c_str(), (unsigned int)str.size(), memStickDirFile)) { - ERROR_LOG(SYSTEM, "Failed to write memstick path '%s' to '%s'", newMemstickFolder.c_str(), memStickDirFile.c_str()); - // Not sure what to do if this file can't be written. Disk full? - } - -#if PPSSPP_PLATFORM(UWP) - } -#endif - - // Save so the settings, at least, are transferred. - g_Config.memStickDirectory = newMemstickFolder; - g_Config.SetSearchPath(GetSysDirectory(DIRECTORY_SYSTEM)); - g_Config.UpdateIniLocation(); - - return true; -} - static std::string FormatSpaceString(int64_t space) { if (space >= 0) { char buffer[50]; @@ -506,43 +455,6 @@ void MemStickScreen::update() { } } -// Keep the size with the file, so we can skip overly large ones in the move. -// The user will have to take care of them afterwards, it'll just take too long probably. -struct FileSuffix { - std::string suffix; - u64 fileSize; -}; - -static bool ListFileSuffixesRecursively(const Path &root, const Path &folder, std::vector &dirSuffixes, std::vector &fileSuffixes) { - std::vector files; - if (!File::GetFilesInDir(folder, &files)) { - return false; - } - - for (auto &file : files) { - if (file.isDirectory) { - std::string dirSuffix; - if (root.ComputePathTo(file.fullName, dirSuffix)) { - if (!dirSuffix.empty()) { - dirSuffixes.push_back(dirSuffix); - ListFileSuffixesRecursively(root, folder / file.name, dirSuffixes, fileSuffixes); - } - } else { - ERROR_LOG_REPORT(SYSTEM, "Failed to compute PathTo from '%s' to '%s'", root.c_str(), folder.c_str()); - } - } else { - std::string fileSuffix; - if (root.ComputePathTo(file.fullName, fileSuffix)) { - if (!fileSuffix.empty()) { - fileSuffixes.push_back(FileSuffix{ fileSuffix, file.size }); - } - } - } - } - - return true; -} - ConfirmMemstickMoveScreen::ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup) : newMemstickFolder_(newMemstickFolder), initialSetup_(initialSetup) { existingFilesInNewFolder_ = FolderSeemsToBeUsed(newMemstickFolder); @@ -666,109 +578,9 @@ UI::EventReturn ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams ¶ms) { progressReporter_.Set(T(I18NCat::MEMSTICK, "Starting move...")); moveDataTask_ = Promise::Spawn(&g_threadManager, [&]() -> MoveResult * { - auto ms = GetI18NCategory(I18NCat::MEMSTICK); Path moveSrc = g_Config.memStickDirectory; Path moveDest = newMemstickFolder_; - if (moveSrc.GetFilename() != "PSP") { - moveSrc = moveSrc / "PSP"; - } - if (moveDest.GetFilename() != "PSP") { - moveDest = moveDest / "PSP"; - File::CreateDir(moveDest); - } - - INFO_LOG(SYSTEM, "About to move PSP data from '%s' to '%s'", moveSrc.c_str(), moveDest.c_str()); - - // Search through recursively, listing the files to move and also summing their sizes. - std::vector fileSuffixesToMove; - std::vector directorySuffixesToCreate; - - // NOTE: It's correct to pass moveSrc twice here, it's to keep the root in the recursion. - if (!ListFileSuffixesRecursively(moveSrc, moveSrc, directorySuffixesToCreate, fileSuffixesToMove)) { - // TODO: Handle failure listing files. - std::string error = "Failed to read old directory"; - INFO_LOG(SYSTEM, "%s", error.c_str()); - progressReporter_.Set(ms->T(error.c_str())); - return new MoveResult{ false, error }; - } - - bool dryRun = false; // Useful for debugging. - - size_t failedFiles = 0; - size_t skippedFiles = 0; - - // We're not moving huge files like ISOs during this process, unless - // they can be directly moved, without rewriting the file. - const uint64_t BIG_FILE_THRESHOLD = 24 * 1024 * 1024; - - if (!moveSrc.empty()) { - // Better not interrupt the app while this is happening! - - // Create all the necessary directories. - for (auto &dirSuffix : directorySuffixesToCreate) { - Path dir = moveDest / dirSuffix; - if (dryRun) { - INFO_LOG(SYSTEM, "dry run: Would have created dir '%s'", dir.c_str()); - } else { - INFO_LOG(SYSTEM, "Creating dir '%s'", dir.c_str()); - progressReporter_.Set(dirSuffix); - // Just ignore already-exists errors. - File::CreateDir(dir); - } - } - - for (auto &fileSuffix : fileSuffixesToMove) { - progressReporter_.Set(StringFromFormat("%s (%s)", fileSuffix.suffix.c_str(), NiceSizeFormat(fileSuffix.fileSize).c_str())); - - Path from = moveSrc / fileSuffix.suffix; - Path to = moveDest / fileSuffix.suffix; - - if (fileSuffix.fileSize > BIG_FILE_THRESHOLD) { - // We only move big files if it's fast to do so. - if (dryRun) { - INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s' (%d bytes) if fast", from.c_str(), to.c_str(), (int)fileSuffix.fileSize); - } else { - if (!File::MoveIfFast(from, to)) { - INFO_LOG(SYSTEM, "Skipped moving file '%s' to '%s' (%s)", from.c_str(), to.c_str(), NiceSizeFormat(fileSuffix.fileSize).c_str()); - skippedFiles++; - } else { - INFO_LOG(SYSTEM, "Moved file '%s' to '%s'", from.c_str(), to.c_str()); - } - } - } else { - if (dryRun) { - INFO_LOG(SYSTEM, "dry run: Would have moved '%s' to '%s' (%d bytes)", from.c_str(), to.c_str(), (int)fileSuffix.fileSize); - } else { - // Remove the "from" prefix from the path. - // We have to drop down to string operations for this. - if (!File::Move(from, to)) { - ERROR_LOG(SYSTEM, "Failed to move file '%s' to '%s'", from.c_str(), to.c_str()); - failedFiles++; - // Should probably just bail? - } else { - INFO_LOG(SYSTEM, "Moved file '%s' to '%s'", from.c_str(), to.c_str()); - } - } - } - } - - // Delete all the old, now hopefully empty, directories. - // Hopefully DeleteDir actually fails if it contains a file... - for (auto &dirSuffix : directorySuffixesToCreate) { - Path dir = moveSrc / dirSuffix; - if (dryRun) { - INFO_LOG(SYSTEM, "dry run: Would have deleted dir '%s'", dir.c_str()); - } else { - INFO_LOG(SYSTEM, "Deleting dir '%s'", dir.c_str()); - progressReporter_.Set(dirSuffix); - if (File::Exists(dir)) { - File::DeleteDir(dir); - } - } - } - } - - return new MoveResult{ true, "", failedFiles }; + return MoveDirectoryContentsSafe(moveSrc, moveDest, progressReporter_); }, TaskType::IO_BLOCKING, TaskPriority::HIGH); RecreateViews(); diff --git a/UI/MemStickScreen.h b/UI/MemStickScreen.h index bf30f6373c..d705aa72ee 100644 --- a/UI/MemStickScreen.h +++ b/UI/MemStickScreen.h @@ -27,6 +27,8 @@ #include "Common/UI/UIScreen.h" #include "Common/Thread/Promise.h" +#include "Core/Util/MemStick.h" + #include "UI/MiscScreens.h" class NoticeView; @@ -91,30 +93,6 @@ private: #endif }; -class ProgressReporter { -public: - void Set(const std::string &value) { - std::lock_guard guard(mutex_); - progress_ = value; - } - - std::string Get() { - std::lock_guard guard(mutex_); - return progress_; - } - -private: - std::string progress_; - std::mutex mutex_; -}; - -struct MoveResult { - bool success; // Got through the whole move. - std::string errorMessage; - size_t failedFiles; - size_t skippedFiles; -}; - class ConfirmMemstickMoveScreen : public UIDialogScreenWithBackground { public: ConfirmMemstickMoveScreen(Path newMemstickFolder, bool initialSetup); @@ -141,7 +119,7 @@ private: #endif bool initialSetup_; - ProgressReporter progressReporter_; + MoveProgressReporter progressReporter_; UI::TextView *progressView_ = nullptr; Promise *moveDataTask_ = nullptr; diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 3b8737f8c4..207fa9ad79 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -321,6 +321,7 @@ + @@ -607,6 +608,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 0f70f58548..82bf9b68af 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -1192,6 +1192,10 @@ Util + + + Util + @@ -1868,6 +1872,10 @@ Util + + + Util + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 196a3de1cf..e4e6838e7f 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -676,6 +676,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/MIPS/JitCommon/JitBlockCache.cpp \ $(SRC)/Core/MIPS/JitCommon/JitState.cpp \ $(SRC)/Core/Util/AudioFormat.cpp \ + $(SRC)/Core/Util/MemStick.cpp \ $(SRC)/Core/Util/PortManager.cpp \ $(SRC)/Core/Util/GameDB.cpp \ $(SRC)/Core/Util/GameManager.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index 9538c75fd7..611be9130a 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -742,6 +742,7 @@ SOURCES_CXX += \ $(COREDIR)/System.cpp \ $(COREDIR)/ThreadPools.cpp \ $(COREDIR)/Util/BlockAllocator.cpp \ + $(COREDIR)/Util/MemStick.cpp \ $(COREDIR)/Util/PPGeDraw.cpp \ $(COREDIR)/Util/AudioFormat.cpp \ $(COREDIR)/Util/PortManager.cpp \