mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-07 13:13:24 +02:00
These two were lower case for legacy reasons, unlike every other directory, which actually matters on case sensitive file systems like the one iOS uses. An existing lower case directory is still used if there's no upper case one, so nobody has to move their files. Fixes #20527 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JvJR8oJNSCimCM9KXVLjfq
267 lines
8.4 KiB
C++
267 lines
8.4 KiB
C++
#include <algorithm>
|
|
#include <string_view>
|
|
|
|
#include "Common/File/Path.h"
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/System/System.h"
|
|
#include "Common/Log.h"
|
|
#include "Core/Util/PathUtil.h"
|
|
#include "Core/Util/DarwinFileSystemServices.h"
|
|
#include "Core/Config.h"
|
|
#include "Common/VR/PPSSPPVR.h"
|
|
|
|
bool HasParentDirComponent(std::string_view path) {
|
|
for (size_t i = 0; i < path.size(); ) {
|
|
size_t end = path.find_first_of("/\\", i);
|
|
size_t len = end == std::string_view::npos ? path.size() - i : end - i;
|
|
if (len == 2 && path[i] == '.' && path[i + 1] == '.')
|
|
return true;
|
|
if (end == std::string_view::npos)
|
|
break;
|
|
i = end + 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool HasPathTraversal(std::string_view path) {
|
|
if (path.find_first_of("/\\") != std::string_view::npos)
|
|
return true;
|
|
return path == "." || path == "..";
|
|
}
|
|
|
|
Path FindConfigFile(const Path &searchPath, std::string_view baseFilename, bool *exists) {
|
|
// Don't search for an absolute path.
|
|
if (baseFilename.size() > 1 && baseFilename[0] == '/') {
|
|
Path path(baseFilename);
|
|
*exists = File::Exists(path);
|
|
return path;
|
|
}
|
|
#ifdef _WIN32
|
|
// Handle paths starting with a drive letter.
|
|
if (baseFilename.size() > 3 && baseFilename[1] == ':' && (baseFilename[2] == '/' || baseFilename[2] == '\\')) {
|
|
Path path(baseFilename);
|
|
*exists = File::Exists(path);
|
|
return path;
|
|
}
|
|
#endif
|
|
|
|
Path filename = searchPath / baseFilename;
|
|
if (File::Exists(filename)) {
|
|
*exists = true;
|
|
return filename;
|
|
}
|
|
*exists = false;
|
|
// Make sure at least the directory it's supposed to be in exists.
|
|
Path parent = filename.NavigateUp();
|
|
|
|
// We try to create the path and ignore if it fails (already exists).
|
|
if (parent != GetSysDirectory(DIRECTORY_SYSTEM)) {
|
|
File::CreateFullPath(parent);
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
// The custom shader and theme directories are lower case for legacy reasons, which is confusing
|
|
// since every other directory is upper case - and on a case sensitive file system, like the one
|
|
// iOS uses, the case actually matters. So we prefer the upper case name now, but keep using a
|
|
// lower case directory that already exists, so nobody has to move their files.
|
|
static Path PreferUpperCaseDir(const Path &parent, const char *upperCase, const char *lowerCase) {
|
|
const Path upper = parent / upperCase;
|
|
if (File::Exists(upper)) {
|
|
return upper;
|
|
}
|
|
const Path lower = parent / lowerCase;
|
|
if (File::Exists(lower)) {
|
|
return lower;
|
|
}
|
|
return upper;
|
|
}
|
|
|
|
Path GetSysDirectory(PSPDirectories directoryType) {
|
|
const Path &memStickDirectory = g_Config.memStickDirectory;
|
|
Path pspDirectory;
|
|
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";
|
|
}
|
|
|
|
switch (directoryType) {
|
|
case DIRECTORY_PSP:
|
|
return pspDirectory;
|
|
case DIRECTORY_CHEATS:
|
|
return pspDirectory / "Cheats";
|
|
case DIRECTORY_GAME:
|
|
return pspDirectory / "GAME";
|
|
case DIRECTORY_SAVEDATA:
|
|
return pspDirectory / "SAVEDATA";
|
|
case DIRECTORY_SCREENSHOT:
|
|
return pspDirectory / "SCREENSHOT";
|
|
case DIRECTORY_SYSTEM:
|
|
return pspDirectory / "SYSTEM";
|
|
case DIRECTORY_PAUTH:
|
|
return memStickDirectory / "PAUTH"; // This one's at the root...
|
|
case DIRECTORY_EXDATA:
|
|
return memStickDirectory / "EXDATA"; // This one's traditionally at the root...
|
|
case DIRECTORY_DUMP:
|
|
return pspDirectory / "SYSTEM/DUMP";
|
|
case DIRECTORY_SAVESTATE:
|
|
return pspDirectory / "PPSSPP_STATE";
|
|
case DIRECTORY_CACHE:
|
|
return pspDirectory / "SYSTEM/CACHE";
|
|
case DIRECTORY_TEXTURES:
|
|
return pspDirectory / "TEXTURES";
|
|
case DIRECTORY_PLUGINS:
|
|
return pspDirectory / "PLUGINS";
|
|
case DIRECTORY_APP_CACHE:
|
|
if (!g_Config.appCacheDirectory.empty()) {
|
|
return g_Config.appCacheDirectory;
|
|
}
|
|
return pspDirectory / "SYSTEM/CACHE";
|
|
case DIRECTORY_VIDEO:
|
|
return pspDirectory / "VIDEO";
|
|
case DIRECTORY_AUDIO:
|
|
return pspDirectory / "AUDIO";
|
|
case DIRECTORY_CUSTOM_SHADERS:
|
|
return PreferUpperCaseDir(pspDirectory, "SHADERS", "shaders");
|
|
case DIRECTORY_CUSTOM_THEMES:
|
|
return PreferUpperCaseDir(pspDirectory, "THEMES", "themes");
|
|
case DIRECTORY_NAND:
|
|
return pspDirectory / "NAND";
|
|
|
|
case DIRECTORY_MEMSTICK_ROOT:
|
|
return g_Config.memStickDirectory;
|
|
// Just return the memory stick root if we run into some sort of problem.
|
|
default:
|
|
ERROR_LOG(Log::FileSystem, "Unknown directory type.");
|
|
return g_Config.memStickDirectory;
|
|
}
|
|
}
|
|
|
|
bool CreateSysDirectories() {
|
|
#if PPSSPP_PLATFORM(ANDROID)
|
|
const bool createNoMedia = true;
|
|
#else
|
|
const bool createNoMedia = false;
|
|
#endif
|
|
|
|
Path pspDir = GetSysDirectory(DIRECTORY_PSP);
|
|
INFO_LOG(Log::IO, "Creating '%s' and subdirs:", pspDir.c_str());
|
|
File::CreateFullPath(pspDir);
|
|
if (!File::Exists(pspDir)) {
|
|
INFO_LOG(Log::IO, "Not a workable memstick directory (%s). Giving up for now", pspDir.c_str());
|
|
return false;
|
|
}
|
|
|
|
// Create the default directories that a real PSP creates. Good for homebrew so they can
|
|
// expect a standard environment. Skipping THEME though, that's pointless.
|
|
static const PSPDirectories sysDirs[] = {
|
|
DIRECTORY_CHEATS,
|
|
DIRECTORY_SAVEDATA,
|
|
DIRECTORY_SAVESTATE,
|
|
DIRECTORY_GAME,
|
|
DIRECTORY_SYSTEM,
|
|
DIRECTORY_TEXTURES,
|
|
DIRECTORY_PLUGINS,
|
|
DIRECTORY_CACHE,
|
|
};
|
|
|
|
for (auto dir : sysDirs) {
|
|
Path path = GetSysDirectory(dir);
|
|
File::CreateFullPath(path);
|
|
if (createNoMedia) {
|
|
// Create a nomedia file in each specified subdirectory.
|
|
File::CreateEmptyFile(path / ".nomedia");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Path GetGameConfigFilePath(const Path &searchPath, std::string_view gameId, bool *exists) {
|
|
std::string_view ppssppIniFilename = IsVREnabled() ? "_ppssppvr.ini" : "_ppsspp.ini";
|
|
std::string iniFileName = join(gameId, ppssppIniFilename);
|
|
Path iniFileNameFull = FindConfigFile(searchPath, iniFileName, exists);
|
|
return iniFileNameFull;
|
|
}
|
|
|
|
// On iOS, the path to the app documents directory changes on each launch.
|
|
// Example path:
|
|
// /var/mobile/Containers/Data/Application/0E0E89DE-8D8E-485A-860C-700D8BC87B86/Documents/PSP/GAME/SuicideBarbie
|
|
// The GUID part changes on each launch.
|
|
bool TryUpdateSavedPath(Path *path) {
|
|
#if PPSSPP_PLATFORM(IOS) && !defined(__LIBRETRO__)
|
|
// DEBUG_LOG(Log::Loader, "Original path: %s", path->c_str());
|
|
std::string pathStr = path->ToString();
|
|
if (startsWith(pathStr, "/private/var/mobile/Containers/Data/Application/")) {
|
|
// For in-application files only, strip off the /private prefix if present - it's sometimes there, sometimes not.
|
|
pathStr = pathStr.substr(8);
|
|
}
|
|
|
|
const std::string_view applicationRoot = "/var/mobile/Containers/Data/Application/";
|
|
if (startsWith(pathStr, applicationRoot)) {
|
|
size_t documentsPos = pathStr.find("/Documents/");
|
|
if (documentsPos == std::string::npos) {
|
|
return false;
|
|
}
|
|
std::string memstick = g_Config.memStickDirectory.ToString();
|
|
size_t memstickDocumentsPos = memstick.find("/Documents"); // Note: No trailing slash, or we won't find it.
|
|
*path = Path(memstick.substr(0, memstickDocumentsPos) + pathStr.substr(documentsPos));
|
|
}
|
|
|
|
if (File::Exists(*path)) {
|
|
return true;
|
|
}
|
|
|
|
// Still doesn't exist? Maybe got de-authorized
|
|
// Try to "unlock" the path before the file loader hits it
|
|
Path newFilename = DarwinFileSystemServices::reauthorizeBookmarkByPath(*path);
|
|
if (!newFilename.empty()) {
|
|
INFO_LOG(Log::UI, "Bookmark rename: %s -> %s", path->c_str(), newFilename.c_str());
|
|
*path = newFilename;
|
|
return true;
|
|
}
|
|
|
|
// Path can't be auto-updated.
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
Path GetFailedBackendsDir() {
|
|
Path failedBackendsDir;
|
|
if (System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS)) {
|
|
failedBackendsDir = GetSysDirectory(DIRECTORY_APP_CACHE);
|
|
} else {
|
|
failedBackendsDir = GetSysDirectory(DIRECTORY_SYSTEM);
|
|
}
|
|
return failedBackendsDir;
|
|
}
|
|
|
|
std::string GetFriendlyPath(Path path, const Path &rootMatch, std::string_view rootDisplay) {
|
|
const Path &root = rootMatch.empty() ? g_Config.memStickDirectory : rootMatch;
|
|
|
|
// Show relative to memstick root if there.
|
|
if (path.StartsWith(root)) {
|
|
std::string p;
|
|
if (root.ComputePathTo(path, p)) {
|
|
return join(rootDisplay, p);
|
|
}
|
|
std::string str = path.ToString();
|
|
if (root.size() < str.length()) {
|
|
return join(rootDisplay, str.substr(root.size()));
|
|
} else {
|
|
return std::string(rootDisplay);
|
|
}
|
|
}
|
|
|
|
#if !PPSSPP_PLATFORM(ANDROID) && (PPSSPP_PLATFORM(LINUX) || PPSSPP_PLATFORM(MAC))
|
|
std::string str = path.ToString();
|
|
char *home = getenv("HOME");
|
|
if (home != nullptr && !strncmp(str.c_str(), home, strlen(home))) {
|
|
return "~" + str.substr(strlen(home));
|
|
}
|
|
#endif
|
|
return path.ToVisualString();
|
|
}
|