MemStickScreen: Refuse folders inside a memory stick's PSP folder

Picking the new memory stick folder is checked by CheckNewMemstickFolder(),
which compares locations across content URIs and Android storage paths,
case-insensitively and on whole folder names:

- Inside the current memory stick's PSP folder: refused. This used to be a
  warning only, compared by string prefix (so "Games2" was "inside"
  "Games"), and missed URIs from a different volume or a native path.
- Inside some other memory stick's PSP folder (like after a reinstall):
  refused. Found on disk among the parents of native paths, and from the
  names for picked trees, whose parents we can't look at.
- Same PSP folder as the current one (a PSP folder and its parent), or the
  current one inside the new one: allowed, without moving data.

GetPSPFolderOf() is the PSP folder rule GetSysDirectory() uses, now shared
with the move, which compared "PSP" case-sensitively and so could move data
into psp/PSP. FolderSeemsToBeUsed() looks in that folder too, and "Move
Data" starts unchecked when the new folder already has data, since merging
overwrites it.

Switching to a folder without a config no longer resets settings to the
defaults: the current ones are written there instead. Typing a manual path
without the leading slash works again (the fixup was unreachable).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-06 14:53:36 +01:00
1 parent b69424e8f2
commit 1832c9aced
8 files changed
+294 -46

No files matched your search

+1
View File
@@ -798,6 +798,7 @@ public:
void SetSearchPath(const Path &path);
void UpdateIniLocation(const char *iniFileName = nullptr, const char *controllerIniFilename = nullptr);
const Path &IniFilename() const { return iniFilename_; }
bool SupportsUpgradeCheck() const;
void CheckForUpdate();
+160 -10
View File
@@ -1,6 +1,8 @@
#include <algorithm>
#include <cstring>
#include <vector>
#include "Common/File/AndroidContentURI.h"
#include "Common/File/Path.h"
#include "Common/File/FileUtil.h"
#include "Common/File/DirListing.h"
@@ -12,18 +14,162 @@
#include "Common/Data/Text/Parsers.h"
#include "Core/Util/MemStick.h"
#include "Core/Util/PathUtil.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;
// Inspect the potential new folder, quickly. We could add more criteria like checking for actual savegames.
const Path pspFolder = GetPSPFolderOf(newMemstickFolder);
return File::Exists(pspFolder / "SAVEDATA") || File::Exists(pspFolder / "SYSTEM");
}
// Checked by string rather than Type(), which only says CONTENT_URI on Android, so the checks below can
// be unit tested anywhere.
static bool IsContentURI(const Path &path) {
return startsWith(path.ToString(), "content://");
}
// A form of a folder's location that compares between content URIs and native paths: "volume:path" for
// Android shared storage, the plain path otherwise. Empty if the location can't be told.
static std::string LocationKey(const Path &path) {
std::string key;
if (IsContentURI(path)) {
AndroidContentURI uri(path.ToString());
// Other providers (like Downloads) use opaque document IDs.
if (uri.Provider() != "com.android.externalstorage.documents") {
return std::string();
}
key = uri.FilePath().empty() ? uri.RootPath() : uri.FilePath();
} else if (path.Type() == PathType::NATIVE) {
key = path.ToString();
// Map Android storage paths to the volume IDs that content URIs use.
static const std::string_view primaryPrefixes[] = { "/storage/emulated/0", "/sdcard", "/storage/self/primary", "/mnt/sdcard" };
bool mapped = false;
for (std::string_view prefix : primaryPrefixes) {
if (key == prefix || startsWith(key, std::string(prefix) + "/")) {
key = "primary:" + key.substr(std::min(key.size(), prefix.size() + 1));
mapped = true;
break;
}
}
if (!mapped && startsWith(key, "/storage/")) {
// SD cards and USB drives: /storage/1234-ABCD/...
const size_t volStart = strlen("/storage/");
const size_t slash = key.find('/', volStart);
const std::string volume = key.substr(volStart, slash == std::string::npos ? std::string::npos : slash - volStart);
if (!volume.empty() && volume != "emulated" && volume != "self") {
key = volume + ":" + (slash == std::string::npos ? std::string() : key.substr(slash + 1));
}
}
} else {
return std::string();
}
while (key.size() > 1 && key.back() == '/') {
key.pop_back();
}
return key;
}
// True if child is strictly inside parent. Case-insensitive, like Android's shared storage.
static bool KeyIsInside(std::string_view child, std::string_view parent) {
if (parent.empty() || child.size() <= parent.size() || !startsWithNoCase(child, parent)) {
return false;
}
const char last = parent.back();
return last == '/' || last == ':' || child[parent.size()] == '/';
}
// LocationKey() of GetPSPFolderOf(folder), by the same rule.
static std::string PSPFolderKey(const Path &folder) {
const std::string key = LocationKey(folder);
if (key.empty()) {
return key;
}
// Path::GetFilename() is GetLastPart() for content URIs.
const std::string name = IsContentURI(folder) ? AndroidContentURI(folder.ToString()).GetLastPart() : folder.GetFilename();
if (equalsNoCase(name, "PSP")) {
return key;
}
return key + (key.back() == ':' || key.back() == '/' ? "PSP" : "/PSP");
}
static bool IsPSPSubfolderName(std::string_view name) {
static const std::string_view names[] = {
"SAVEDATA", "GAME", "SYSTEM", "PPSSPP_STATE", "TEXTURES", "PLUGINS", "CHEATS", "SCREENSHOT",
"MUSIC", "PHOTO", "VIDEO", "COMMON",
};
for (std::string_view n : names) {
if (equalsNoCase(name, n)) {
return true;
}
}
return false;
}
// Works from the name alone, for folders whose parents we can't look at (a picked content URI tree):
// a PSP folder with one of its usual subfolders below it.
static bool KeyLooksInsidePSPFolder(std::string_view key) {
const size_t colon = key.find(':');
std::vector<std::string_view> parts;
SplitString(key.substr(colon == std::string_view::npos ? 0 : colon + 1), '/', parts);
parts.erase(std::remove(parts.begin(), parts.end(), std::string_view()), parts.end());
for (size_t i = 0; i + 1 < parts.size(); i++) {
if (equalsNoCase(parts[i], "PSP") && IsPSPSubfolderName(parts[i + 1])) {
return true;
}
}
return false;
}
static bool IsInsideOtherMemstick(const Path &folder, const std::string &key) {
if (KeyLooksInsidePSPFolder(key)) {
return true;
}
if (folder.Type() != PathType::NATIVE || IsContentURI(folder)) {
return false;
}
// Native paths: look for a memory stick in use among the parents.
Path dir = folder;
for (int depth = 0; depth < 64 && dir.CanNavigateUp(); depth++) {
const Path up = dir.NavigateUp();
if (up == dir || up.empty()) {
break;
}
dir = up;
if (KeyIsInside(key, PSPFolderKey(dir)) && FolderSeemsToBeUsed(dir)) {
return true;
}
}
return false;
}
MemstickFolderCheck CheckNewMemstickFolder(const Path &newFolder, const Path &currentFolder) {
const std::string newKey = LocationKey(newFolder);
if (newKey.empty()) {
return MemstickFolderCheck::OK;
}
const std::string newPSPKey = PSPFolderKey(newFolder);
std::string curPSPKey;
if (!currentFolder.empty()) {
curPSPKey = PSPFolderKey(currentFolder);
if (KeyIsInside(newPSPKey, curPSPKey)) {
return MemstickFolderCheck::INSIDE_CURRENT;
}
}
if (IsInsideOtherMemstick(newFolder, newKey)) {
return MemstickFolderCheck::INSIDE_OTHER;
}
if (!curPSPKey.empty()) {
if (equalsNoCase(newPSPKey, curPSPKey)) {
return MemstickFolderCheck::SAME_PSP_FOLDER;
}
if (KeyIsInside(curPSPKey, newPSPKey)) {
return MemstickFolderCheck::CONTAINS_CURRENT;
}
}
return MemstickFolderCheck::OK;
}
bool SwitchMemstickFolderTo(Path newMemstickFolder) {
@@ -145,13 +291,17 @@ std::string MoveProgressReporter::Format() {
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);
moveSrc = GetPSPFolderOf(moveSrc);
moveDest = GetPSPFolderOf(moveDest);
// The UI doesn't offer these (see CheckNewMemstickFolder), but moving a folder into itself would be a mess.
const std::string srcKey = LocationKey(moveSrc);
const std::string destKey = LocationKey(moveDest);
if (!srcKey.empty() && !destKey.empty() && (equalsNoCase(srcKey, destKey) || KeyIsInside(srcKey, destKey) || KeyIsInside(destKey, srcKey))) {
ERROR_LOG(Log::System, "Refusing to move PSP data from '%s' to '%s'", moveSrc.c_str(), moveDest.c_str());
return new MoveResult{ false, "Source and destination overlap" };
}
File::CreateDir(moveDest);
INFO_LOG(Log::System, "About to move PSP data from '%s' to '%s'", moveSrc.c_str(), moveDest.c_str());
+15
View File
@@ -32,6 +32,21 @@ struct MoveResult {
size_t skippedFiles;
};
enum class MemstickFolderCheck {
OK,
// A different folder with the same PSP folder as the current one (a PSP folder and its parent). Nothing to move.
SAME_PSP_FOLDER,
// The current memory stick is inside the new one's PSP folder, so its data can't be moved there.
CONTAINS_CURRENT,
// Not allowed: inside the current memory stick's PSP folder.
INSIDE_CURRENT,
// Not allowed: inside the PSP folder of some other memory stick.
INSIDE_OTHER,
};
// Checks a folder the user picked as the new memory stick. currentFolder may be empty.
MemstickFolderCheck CheckNewMemstickFolder(const Path &newFolder, const Path &currentFolder);
bool FolderSeemsToBeUsed(const Path &newMemstickFolder);
bool SwitchMemstickFolderTo(Path newMemstickFolder);
MoveResult *MoveDirectoryContentsSafe(Path moveSrc, Path moveDest, MoveProgressReporter &progressReporter);
+8 -6
View File
@@ -94,15 +94,17 @@ static Path PreferUpperCaseDir(const Path &parent, const char *upperCase, const
return upper;
}
Path GetSysDirectory(PSPDirectories directoryType) {
const Path &memStickDirectory = g_Config.memStickDirectory;
Path pspDirectory;
Path GetPSPFolderOf(const Path &memStickDirectory) {
if (!strcasecmp(memStickDirectory.GetFilename().c_str(), "PSP")) {
// Let's strip this off, to easily allow choosing a root directory named "PSP" on Android.
pspDirectory = memStickDirectory;
} else {
pspDirectory = memStickDirectory / "PSP";
return memStickDirectory;
}
return memStickDirectory / "PSP";
}
Path GetSysDirectory(PSPDirectories directoryType) {
const Path &memStickDirectory = g_Config.memStickDirectory;
const Path pspDirectory = GetPSPFolderOf(memStickDirectory);
switch (directoryType) {
case DIRECTORY_PSP:
+3
View File
@@ -48,6 +48,9 @@ bool HasPathTraversal(std::string_view path);
bool IsSafePathComponent(std::string_view name);
Path FindConfigFile(const Path &searchPath, std::string_view baseFilename, bool *exists);
// The PSP folder of a memory stick. A folder named PSP is used as it is (the legacy layout, where its
// parent acts as the root), anything else gets a PSP folder inside.
Path GetPSPFolderOf(const Path &memStickDirectory);
Path GetSysDirectory(PSPDirectories directoryType);
bool CreateSysDirectories();
Path GetGameConfigFilePath(const Path &searchPath, std::string_view gameId, bool *exists);
+45 -29
View File
@@ -311,9 +311,13 @@ void MemStickScreen::SetFolderManually(UI::EventParams &params) {
auto di = GetI18NCategory(I18NCat::DIALOG);
std::string newPath(value);
// Apparently some users got used to leaving out the leading /.
if (!newPath.empty() && newPath[0] != '/') {
newPath = "/" + newPath;
}
size_t pos = newPath.find_last_not_of('/');
// Gotta have at least something but a /, and also needs to start with a /.
if (newPath.empty() || pos == std::string::npos || newPath[0] != '/') {
// Gotta have at least something but a /.
if (pos == std::string::npos) {
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("ChangingMemstickPathInvalid", "That path couldn't be used to save Memory Stick files."), 5.0f);
return;
}
@@ -329,15 +333,6 @@ void MemStickScreen::SetFolderManually(UI::EventParams &params) {
Path pendingMemStickFolder(newPath);
if (!File::Exists(pendingMemStickFolder)) {
// Try to fix the path string, apparently some users got used to leaving out the /.
if (newPath[0] != '/') {
newPath = "/" + newPath;
}
pendingMemStickFolder = Path(newPath);
}
if (!File::Exists(pendingMemStickFolder) && pendingMemStickFolder.Type() == PathType::NATIVE) {
// Still no path? Try to automatically fix the case.
std::string oldNewPath = newPath;
@@ -352,7 +347,7 @@ void MemStickScreen::SetFolderManually(UI::EventParams &params) {
if (pendingMemStickFolder == g_Config.memStickDirectory) {
// Same directory as before - all good. Nothing to do.
TriggerFinish(DialogResult::DR_OK);
done_ = true;
return;
}
@@ -419,6 +414,9 @@ void MemStickScreen::Browse(UI::EventParams &params) {
System_BrowseForFolder(GetRequesterToken(), mm->T("Choose folder"), g_Config.memStickDirectory, [this](std::string_view value, int) {
Path pendingMemStickFolder(value);
INFO_LOG(Log::System, "Got folder: '%s' (old: %s)", pendingMemStickFolder.c_str(), g_Config.memStickDirectory.c_str());
if (pendingMemStickFolder.empty()) {
return;
}
// Browse finished. Let's pop up the confirmation dialog.
if (!pendingMemStickFolder.empty() && pendingMemStickFolder == g_Config.memStickDirectory && File::IsDirectory(pendingMemStickFolder)) {
auto di = GetI18NCategory(I18NCat::DIALOG);
@@ -473,18 +471,12 @@ ConfirmMemstickMoveScreen::ConfirmMemstickMoveScreen(const Path &newMemstickFold
: newMemstickFolder_(newMemstickFolder), initialSetup_(initialSetup), progressReporter_() {
const Path &oldMemstickFolder = g_Config.memStickDirectory;
existingFilesInNewFolder_ = FolderSeemsToBeUsed(newMemstickFolder);
if (!oldMemstickFolder.empty()) {
folderConflict_ = newMemstickFolder != oldMemstickFolder && newMemstickFolder.StartsWithGlobalAndNotEqual(oldMemstickFolder);
} else {
folderConflict_ = false;
}
check_ = CheckNewMemstickFolder(newMemstickFolder, oldMemstickFolder);
INFO_LOG(Log::System, "Old: '%s'", oldMemstickFolder.c_str());
INFO_LOG(Log::System, "New: '%s'", newMemstickFolder.c_str());
INFO_LOG(Log::System, "New: '%s' (check: %d)", newMemstickFolder.c_str(), (int)check_);
// TODO: If you reinstall the app and start by selecting a subfolder of the PSP folder, we don't detect and warn about that -
// we can only warn if there's a known previous folder :(
if (initialSetup_) {
// Moving into a folder that already has data would overwrite it, so that has to be asked for.
if (initialSetup_ || existingFilesInNewFolder_ || !CanMoveData()) {
moveData_ = false;
}
@@ -542,8 +534,21 @@ void ConfirmMemstickMoveScreen::CreateViews() {
}
}
if (folderConflict_) {
leftColumn->Add(new NoticeView(NoticeLevel::WARN, ms->T("The new folder is inside the previous one"), ""));
switch (check_) {
case MemstickFolderCheck::INSIDE_CURRENT:
leftColumn->Add(new NoticeView(NoticeLevel::ERROR, ms->T("The new folder is inside the previous one"), ""));
break;
case MemstickFolderCheck::INSIDE_OTHER:
leftColumn->Add(new NoticeView(NoticeLevel::ERROR, ms->T("InsideOtherMemstick", "This folder is inside the PSP folder of another memory stick. Choose that memory stick's folder instead."), ""));
break;
case MemstickFolderCheck::SAME_PSP_FOLDER:
leftColumn->Add(new NoticeView(NoticeLevel::INFO, ms->T("SamePSPFolder", "Contains the same PSP folder as the current one. Nothing will be moved."), ""));
break;
case MemstickFolderCheck::CONTAINS_CURRENT:
leftColumn->Add(new NoticeView(NoticeLevel::WARN, ms->T("ContainsCurrentMemstick", "The current folder is inside this one, so its data can't be moved here"), ""));
break;
case MemstickFolderCheck::OK:
break;
}
if (!error_.empty()) {
@@ -562,12 +567,14 @@ void ConfirmMemstickMoveScreen::CreateViews() {
}
if (!moveDataTask_) {
if (!initialSetup_) {
if (!initialSetup_ && CanMoveData()) {
leftColumn->Add(new CheckBox(&moveData_, ms->T("Move Data")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnMoveDataClick);
}
auto di = GetI18NCategory(I18NCat::DIALOG);
leftColumn->Add(new Choice(di->T("OK")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnConfirm);
if (!IsBlocked()) {
leftColumn->Add(new Choice(di->T("OK")))->OnClick.Handle(this, &ConfirmMemstickMoveScreen::OnConfirm);
}
leftColumn->Add(new Choice(di->T("Back")))->OnClick.Add([this](UI::EventParams &params) {
if (moveDataTask_ && !moveDataTask_->Poll()) {
return;
@@ -633,8 +640,11 @@ void ConfirmMemstickMoveScreen::OnConfirm(UI::EventParams &params) {
// Should probably be done on a background thread so we can show some UI.
// So we probably need another screen for this with a progress bar..
// If the directory itself is called PSP, don't go below.
if (IsBlocked()) {
return;
}
if (moveData_) {
if (moveData_ && CanMoveData()) {
progressReporter_.SetProgress(T(I18NCat::MEMSTICK, "Starting move..."));
moveDataTask_ = Promise<MoveResult *>::Spawn(&g_threadManager, [&]() -> MoveResult * {
@@ -666,8 +676,14 @@ void ConfirmMemstickMoveScreen::FinishFolderMove() {
INFO_LOG(Log::System, "Move from '%s' to '%s' complete. Updating config.", oldMemstickFolder.c_str(), newMemstickFolder_.c_str());
// If the chosen folder already had a config, reload it!
g_Config.Load();
// If the chosen folder already had a config, reload it! Otherwise keep the current settings, and
// write them there.
if (File::Exists(g_Config.IniFilename())) {
g_Config.Load();
} else {
INFO_LOG(Log::System, "No config at '%s', keeping the current settings", g_Config.IniFilename().c_str());
g_Config.Save("MemstickPathChanged");
}
// If the current browser directory is the old memstick folder, drop it.
if (g_Config.currentDirectory == oldMemstickFolder) {
+8 -1
View File
@@ -117,9 +117,16 @@ private:
void OnConfirm(UI::EventParams &params);
bool IsBlocked() const {
return check_ == MemstickFolderCheck::INSIDE_CURRENT || check_ == MemstickFolderCheck::INSIDE_OTHER;
}
bool CanMoveData() const {
return check_ == MemstickFolderCheck::OK;
}
Path newMemstickFolder_;
bool existingFilesInNewFolder_;
bool folderConflict_;
MemstickFolderCheck check_ = MemstickFolderCheck::OK;
#if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__)
bool moveData_ = false;
+54
View File
@@ -127,6 +127,7 @@
#include "Core/ControlMapper.h"
#include "Core/HLE/sceCtrl.h"
#include "Core/HLE/sceUsbMic.h"
#include "Core/Util/MemStick.h"
#include "Core/Util/StorageTest.h"
#include "Core/Util/PathUtil.h"
#include "Core/Util/PSARUnpack.h"
@@ -2954,6 +2955,58 @@ static bool TestStorage() {
return true;
}
// A picked folder, the way the folder browser hands it back. docPath is like "primary:Games/PSP".
static Path PickedTreeURI(std::string_view docPath) {
const std::string enc = UriEncode(docPath);
return Path("content://com.android.externalstorage.documents/tree/" + enc + "/document/" + enc);
}
static bool TestMemstickFolderCheck() {
using C = MemstickFolderCheck;
const Path none;
// Inside the current memory stick's PSP folder, case-insensitively.
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:Games/PSP/SAVEDATA"), PickedTreeURI("primary:Games")), (int)C::INSIDE_CURRENT);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:games/psp/stuff"), PickedTreeURI("primary:Games")), (int)C::INSIDE_CURRENT);
// A folder named PSP is the PSP folder itself.
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:PSP/stuff"), PickedTreeURI("primary:PSP")), (int)C::INSIDE_CURRENT);
// Next to the PSP folder, or a name that only starts the same, is fine.
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:Games/ISOs"), PickedTreeURI("primary:Games")), (int)C::OK);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:GamesX"), PickedTreeURI("primary:Games")), (int)C::OK);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("1234-ABCD:PSP"), PickedTreeURI("primary:PSP")), (int)C::OK);
// Inside some other memory stick, with no current one (a reinstall).
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:Stuff/PSP/SAVEDATA"), none), (int)C::INSIDE_OTHER);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("1234-ABCD:PSP/GAME/x"), none), (int)C::INSIDE_OTHER);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:Stuff/PSP/Mine"), none), (int)C::OK);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:PSP"), none), (int)C::OK);
// Native storage paths and content URIs are compared by location.
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:PSP/GAME"), Path("/storage/emulated/0")), (int)C::INSIDE_CURRENT);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(Path("/storage/emulated/0"), PickedTreeURI("primary:PSP")), (int)C::SAME_PSP_FOLDER);
// Other names for internal storage, typed in on old versions.
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:"), Path("/mnt/sdcard")), (int)C::SAME_PSP_FOLDER);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:PSP"), Path("/storage/self/primary/PSP")), (int)C::SAME_PSP_FOLDER);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("1234-ABCD:Mine/PSP/TEXTURES"), Path("/storage/1234-ABCD/Mine")), (int)C::INSIDE_CURRENT);
// The current memory stick is inside the new one's PSP folder: switching is fine, moving isn't.
EXPECT_EQ_INT((int)CheckNewMemstickFolder(PickedTreeURI("primary:Games"), PickedTreeURI("primary:Games/PSP/Weird")), (int)C::CONTAINS_CURRENT);
// Native paths: a memory stick in use among the parents is found on disk, whatever the folder names.
const Path root = UnitTestTempPath("unittest_memstick_check");
File::DeleteDirRecursively(root);
EXPECT_TRUE(File::CreateFullPath(root / "ms/PSP/SAVEDATA"));
EXPECT_TRUE(File::CreateFullPath(root / "ms/PSP/Mine/deeper"));
EXPECT_EQ_INT((int)CheckNewMemstickFolder(root / "ms/PSP/Mine/deeper", none), (int)C::INSIDE_OTHER);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(root / "ms/PSP", none), (int)C::OK);
EXPECT_EQ_INT((int)CheckNewMemstickFolder(root / "ms", none), (int)C::OK);
EXPECT_TRUE(FolderSeemsToBeUsed(root / "ms"));
EXPECT_TRUE(FolderSeemsToBeUsed(root / "ms/PSP"));
EXPECT_FALSE(FolderSeemsToBeUsed(root / "ms/PSP/Mine"));
File::DeleteDirRecursively(root);
return true;
}
class UnitTestWordWrapper : public WordWrapper {
public:
UnitTestWordWrapper(std::string_view str, float maxW, int flags)
@@ -4341,6 +4394,7 @@ TestItem availableTests[] = {
TEST_ITEM(OverlayHole),
TEST_ITEM(AndroidContentURI),
TEST_ITEM(Storage),
TEST_ITEM(MemstickFolderCheck),
TEST_ITEM(ThreadManager),
TEST_ITEM(WrapText),
TEST_ITEM(TinySet),