diff --git a/Common/File/AndroidStorage.cpp b/Common/File/AndroidStorage.cpp index 277498ab50..d8d6468b91 100644 --- a/Common/File/AndroidStorage.cpp +++ b/Common/File/AndroidStorage.cpp @@ -11,12 +11,15 @@ static jmethodID openContentUri; static jmethodID listContentUriDir; static jmethodID contentUriCreateFile; static jmethodID contentUriCreateDirectory; +static jmethodID contentUriCopyFile; +static jmethodID contentUriMoveFile; static jmethodID contentUriRemoveFile; static jmethodID contentUriRenameFileTo; static jmethodID contentUriGetFileInfo; static jmethodID contentUriFileExists; static jmethodID contentUriGetFreeStorageSpace; static jmethodID filePathGetFreeStorageSpace; +static jmethodID isExternalStoragePreservedLegacy; static jobject g_nativeActivity; @@ -29,13 +32,17 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) { _dbg_assert_(openContentUri); listContentUriDir = env->GetMethodID(env->GetObjectClass(obj), "listContentUriDir", "(Ljava/lang/String;)[Ljava/lang/String;"); _dbg_assert_(listContentUriDir); - contentUriCreateDirectory = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateDirectory", "(Ljava/lang/String;Ljava/lang/String;)Z"); + contentUriCreateDirectory = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateDirectory", "(Ljava/lang/String;Ljava/lang/String;)I"); _dbg_assert_(contentUriCreateDirectory); - contentUriCreateFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateFile", "(Ljava/lang/String;Ljava/lang/String;)Z"); + contentUriCreateFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriCreateFile", "(Ljava/lang/String;Ljava/lang/String;)I"); _dbg_assert_(contentUriCreateFile); - contentUriRemoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriRemoveFile", "(Ljava/lang/String;)Z"); + contentUriCopyFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriCopyFile", "(Ljava/lang/String;Ljava/lang/String;)I"); + _dbg_assert_(contentUriCopyFile); + contentUriRemoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriRemoveFile", "(Ljava/lang/String;)I"); _dbg_assert_(contentUriRemoveFile); - contentUriRenameFileTo = env->GetMethodID(env->GetObjectClass(obj), "contentUriRenameFileTo", "(Ljava/lang/String;Ljava/lang/String;)Z"); + contentUriMoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriMoveFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I"); + _dbg_assert_(contentUriMoveFile); + contentUriRenameFileTo = env->GetMethodID(env->GetObjectClass(obj), "contentUriRenameFileTo", "(Ljava/lang/String;Ljava/lang/String;)I"); _dbg_assert_(contentUriRenameFileTo); contentUriGetFileInfo = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFileInfo", "(Ljava/lang/String;)Ljava/lang/String;"); _dbg_assert_(contentUriGetFileInfo); @@ -45,6 +52,8 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) { _dbg_assert_(contentUriGetFreeStorageSpace); filePathGetFreeStorageSpace = env->GetMethodID(env->GetObjectClass(obj), "filePathGetFreeStorageSpace", "(Ljava/lang/String;)J"); _dbg_assert_(filePathGetFreeStorageSpace); + isExternalStoragePreservedLegacy = env->GetMethodID(env->GetObjectClass(obj), "isExternalStoragePreservedLegacy", "()Z"); + _dbg_assert_(isExternalStoragePreservedLegacy); } bool Android_IsContentUri(const std::string &filename) { @@ -75,49 +84,71 @@ int Android_OpenContentUriFd(const std::string &filename, Android_OpenContentUri return fd; } -bool Android_CreateDirectory(const std::string &rootTreeUri, const std::string &dirName) { +StorageError Android_CreateDirectory(const std::string &rootTreeUri, const std::string &dirName) { if (!g_nativeActivity) { - return false; + return StorageError::UNKNOWN; } auto env = getEnv(); jstring paramRoot = env->NewStringUTF(rootTreeUri.c_str()); jstring paramDirName = env->NewStringUTF(dirName.c_str()); - return env->CallBooleanMethod(g_nativeActivity, contentUriCreateDirectory, paramRoot, paramDirName); + return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriCreateDirectory, paramRoot, paramDirName)); } -bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { +StorageError Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { if (!g_nativeActivity) { - return false; + return StorageError::UNKNOWN; } auto env = getEnv(); jstring paramRoot = env->NewStringUTF(parentTreeUri.c_str()); jstring paramFileName = env->NewStringUTF(fileName.c_str()); - return env->CallBooleanMethod(g_nativeActivity, contentUriCreateFile, paramRoot, paramFileName); + return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriCreateFile, paramRoot, paramFileName)); } -bool Android_RemoveFile(const std::string &fileUri) { +StorageError Android_CopyFile(const std::string &fileUri, const std::string &destParentUri) { if (!g_nativeActivity) { - return false; + return StorageError::UNKNOWN; } auto env = getEnv(); jstring paramFileName = env->NewStringUTF(fileUri.c_str()); - return env->CallBooleanMethod(g_nativeActivity, contentUriRemoveFile, paramFileName); + jstring paramDestParentUri = env->NewStringUTF(destParentUri.c_str()); + return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriCopyFile, paramFileName, paramDestParentUri)); } -bool Android_RenameFileTo(const std::string &fileUri, const std::string &newName) { +StorageError Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri) { if (!g_nativeActivity) { - return false; + return StorageError::UNKNOWN; + } + auto env = getEnv(); + jstring paramFileName = env->NewStringUTF(fileUri.c_str()); + jstring paramSrcParentUri = env->NewStringUTF(srcParentUri.c_str()); + jstring paramDestParentUri = env->NewStringUTF(destParentUri.c_str()); + return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriMoveFile, paramFileName, paramSrcParentUri, paramDestParentUri)); +} + +StorageError Android_RemoveFile(const std::string &fileUri) { + if (!g_nativeActivity) { + return StorageError::UNKNOWN; + } + auto env = getEnv(); + jstring paramFileName = env->NewStringUTF(fileUri.c_str()); + return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriRemoveFile, paramFileName)); +} + +StorageError Android_RenameFileTo(const std::string &fileUri, const std::string &newName) { + if (!g_nativeActivity) { + return StorageError::UNKNOWN; } auto env = getEnv(); jstring paramFileUri = env->NewStringUTF(fileUri.c_str()); jstring paramNewName = env->NewStringUTF(newName.c_str()); - return env->CallBooleanMethod(g_nativeActivity, contentUriRenameFileTo, paramFileUri, paramNewName); + return StorageErrorFromInt(env->CallIntMethod(g_nativeActivity, contentUriRenameFileTo, paramFileUri, paramNewName)); } +// NOTE: Does not set fullName - you're supposed to already know it. static bool ParseFileInfo(const std::string &line, File::FileInfo *fileInfo) { std::vector parts; SplitString(line, '|', parts); - if (parts.size() != 5) { + if (parts.size() != 4) { ERROR_LOG(FILESYS, "Bad format: %s", line.c_str()); return false; } @@ -125,12 +156,11 @@ static bool ParseFileInfo(const std::string &line, File::FileInfo *fileInfo) { fileInfo->isDirectory = parts[0][0] == 'D'; fileInfo->exists = true; sscanf(parts[1].c_str(), "%" PRIu64, &fileInfo->size); - fileInfo->fullName = Path(parts[3]); fileInfo->isWritable = true; // TODO: Should be passed as part of the string. fileInfo->access = fileInfo->isDirectory ? 0666 : 0777; // TODO: For read-only mappings, reflect that here, similarly as with isWritable. uint64_t lastModifiedMs = 0; - sscanf(parts[4].c_str(), "%" PRIu64, &lastModifiedMs); + sscanf(parts[3].c_str(), "%" PRIu64, &lastModifiedMs); // Convert from milliseconds uint32_t lastModified = lastModifiedMs / 1000; @@ -155,6 +185,8 @@ bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *fileInfo) { } const char *charArray = env->GetStringUTFChars(str, 0); bool retval = ParseFileInfo(std::string(charArray), fileInfo); + fileInfo->fullName = Path(fileUri); + env->DeleteLocalRef(str); return retval && fileInfo->exists; } @@ -189,6 +221,8 @@ std::vector Android_ListContentUri(const std::string &path) { if (charArray) { // paranoia File::FileInfo info; if (ParseFileInfo(std::string(charArray), &info)) { + // We can just reconstruct the URI. + info.fullName = Path(path) / info.name; items.push_back(info); } } @@ -224,4 +258,28 @@ int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) { return env->CallLongMethod(g_nativeActivity, filePathGetFreeStorageSpace, param); } +bool Android_IsExternalStoragePreservedLegacy() { + if (!g_nativeActivity) { + return false; + } + auto env = getEnv(); + return env->CallBooleanMethod(g_nativeActivity, isExternalStoragePreservedLegacy); +} + +const char *Android_ErrorToString(StorageError error) { + switch (error) { + case StorageError::SUCCESS: return "SUCCESS"; + case StorageError::UNKNOWN: return "UNKNOWN"; + case StorageError::NOT_FOUND: return "NOT_FOUND"; + case StorageError::DISK_FULL: return "DISK_FULL"; + case StorageError::ALREADY_EXISTS: return "ALREADY_EXISTS"; + default: return "(UNKNOWN)"; + } +} + +#else + +// This string should never appear except on Android. +std::string g_extFilesDir = "(IF YOU SEE THIS THERE'S A BUG)"; + #endif diff --git a/Common/File/AndroidStorage.h b/Common/File/AndroidStorage.h index 5370cb2be2..131c0125d2 100644 --- a/Common/File/AndroidStorage.h +++ b/Common/File/AndroidStorage.h @@ -13,6 +13,23 @@ enum class Android_OpenContentUriMode { READ_WRITE_TRUNCATE = 2, // "rwt" }; +// Matches the constants in PpssppActivity.java. +enum class StorageError { + SUCCESS = 0, + UNKNOWN = -1, + NOT_FOUND = -2, + DISK_FULL = -3, + ALREADY_EXISTS = -4, +}; + +inline StorageError StorageErrorFromInt(int ival) { + if (ival >= 0) { + return StorageError::SUCCESS; + } else { + return (StorageError)ival; + } +} + #if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__) #include @@ -23,14 +40,18 @@ void Android_StorageSetNativeActivity(jobject nativeActivity); bool Android_IsContentUri(const std::string &uri); int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode); -bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName); -bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName); -bool Android_RemoveFile(const std::string &fileUri); -bool Android_RenameFileTo(const std::string &fileUri, const std::string &newName); +StorageError Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName); +StorageError Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName); +StorageError Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri); +StorageError Android_CopyFile(const std::string &fileUri, const std::string &destParentUri); +StorageError Android_RemoveFile(const std::string &fileUri); +StorageError Android_RenameFileTo(const std::string &fileUri, const std::string &newName); bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *info); bool Android_FileExists(const std::string &fileUri); int64_t Android_GetFreeSpaceByContentUri(const std::string &uri); int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath); +bool Android_IsExternalStoragePreservedLegacy(); +const char *Android_ErrorToString(StorageError error); std::vector Android_ListContentUri(const std::string &uri); @@ -38,18 +59,24 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj); #else +extern std::string g_extFilesDir; + // Stub out the Android Storage wrappers, so that we can avoid ifdefs everywhere. inline bool Android_IsContentUri(const std::string &uri) { return false; } inline int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode) { return -1; } -inline bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName) { return false; } -inline bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { return false; } -inline bool Android_RemoveFile(const std::string &fileUri) { return false; } -inline bool Android_RenameFileTo(const std::string &fileUri, const std::string &newName) { return false; } +inline StorageError Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName) { return StorageError::UNKNOWN; } +inline StorageError Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { return StorageError::UNKNOWN; } +inline StorageError Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri) { return StorageError::UNKNOWN; } +inline StorageError Android_CopyFile(const std::string &fileUri, const std::string &destParentUri) { return StorageError::UNKNOWN; } +inline StorageError Android_RemoveFile(const std::string &fileUri) { return StorageError::UNKNOWN; } +inline StorageError Android_RenameFileTo(const std::string &fileUri, const std::string &newName) { return StorageError::UNKNOWN; } inline bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *info) { return false; } inline bool Android_FileExists(const std::string &fileUri) { return false; } inline int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) { return -1; } inline int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) { return -1; } +inline bool Android_IsExternalStoragePreservedLegacy() { return false; } +inline const char *Android_ErrorToString(StorageError error) { return ""; } inline std::vector Android_ListContentUri(const std::string &uri) { return std::vector(); } diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 5468386096..134d84f74b 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -107,7 +107,7 @@ FILE *OpenCFile(const Path &path, const char *mode) { INFO_LOG(COMMON, "Opening content file for read: '%s'", path.c_str()); // Read, let's support this - easy one. int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ); - if (descriptor == -1) { + if (descriptor < 0) { return nullptr; } return fdopen(descriptor, "rb"); @@ -119,7 +119,7 @@ FILE *OpenCFile(const Path &path, const char *mode) { std::string name = path.GetFilename(); if (path.CanNavigateUp()) { Path parent = path.NavigateUp(); - if (!Android_CreateFile(parent.ToString(), name)) { + if (Android_CreateFile(parent.ToString(), name) != StorageError::SUCCESS) { WARN_LOG(COMMON, "Failed to create file '%s' in '%s'", name.c_str(), parent.c_str()); return nullptr; } @@ -133,7 +133,7 @@ FILE *OpenCFile(const Path &path, const char *mode) { // TODO: Support append modes and stuff... For now let's go with the most common one. int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ_WRITE_TRUNCATE); - if (descriptor == -1) { + if (descriptor < 0) { INFO_LOG(COMMON, "Opening '%s' for write failed", path.ToString().c_str()); return nullptr; } @@ -189,7 +189,7 @@ int OpenFD(const Path &path, OpenFlag flags) { std::string name = path.GetFilename(); if (path.CanNavigateUp()) { Path parent = path.NavigateUp(); - if (!Android_CreateFile(parent.ToString(), name)) { + if (Android_CreateFile(parent.ToString(), name) != StorageError::SUCCESS) { WARN_LOG(COMMON, "OpenFD: Failed to create file '%s' in '%s'", name.c_str(), parent.c_str()); return -1; } @@ -223,6 +223,12 @@ int OpenFD(const Path &path, OpenFlag flags) { if (descriptor < 0) { ERROR_LOG(COMMON, "Android_OpenContentUriFd failed: '%s'", path.c_str()); } + + if (flags & OPEN_APPEND) { + // Simply seek to the end of the file to simulate append mode. + lseek(descriptor, 0, SEEK_END); + } + return descriptor; } @@ -390,7 +396,7 @@ bool Delete(const Path &filename) { case PathType::NATIVE: break; // OK case PathType::CONTENT_URI: - return Android_RemoveFile(filename.ToString()); + return Android_RemoveFile(filename.ToString()) == StorageError::SUCCESS; default: return false; } @@ -433,13 +439,19 @@ bool CreateDir(const Path &path) { break; // OK case PathType::CONTENT_URI: { + // NOTE: The Android storage API will simply create a renamed directory (append a number) if it already exists. + // We want to avoid that, so let's just return true if the directory already is there. + if (File::Exists(path)) { + return true; + } + // Convert it to a "CreateDirIn" call, if possible, since that's // what we can do with the storage API. AndroidContentURI uri(path.ToString()); std::string newDirName = uri.GetLastPart(); if (uri.NavigateUp()) { INFO_LOG(COMMON, "Calling Android_CreateDirectory(%s, %s)", uri.ToString().c_str(), newDirName.c_str()); - return Android_CreateDirectory(uri.ToString(), newDirName); + return Android_CreateDirectory(uri.ToString(), newDirName) == StorageError::SUCCESS; } else { // Bad path - can't create this directory. WARN_LOG(COMMON, "CreateDir failed: '%s'", path.c_str()); @@ -526,7 +538,7 @@ bool DeleteDir(const Path &path) { case PathType::NATIVE: break; // OK case PathType::CONTENT_URI: - return Android_RemoveFile(path.ToString()); + return Android_RemoveFile(path.ToString()) == StorageError::SUCCESS; default: return false; } @@ -565,13 +577,14 @@ bool Rename(const Path &srcFilename, const Path &destFilename) { break; case PathType::CONTENT_URI: // Content URI: Can only rename if in the same folder. + // TODO: Fallback to move + rename? Or do we even care about that use case? if (srcFilename.GetDirectory() != destFilename.GetDirectory()) { INFO_LOG(COMMON, "Content URI rename: Directories not matching, failing. %s --> %s", srcFilename.c_str(), destFilename.c_str()); return false; } INFO_LOG(COMMON, "Content URI rename: %s --> %s", srcFilename.c_str(), destFilename.c_str()); - return Android_RenameFileTo(srcFilename.ToString(), destFilename.GetFilename()); + return Android_RenameFileTo(srcFilename.ToString(), destFilename.GetFilename()) == StorageError::SUCCESS; default: return false; } @@ -594,26 +607,23 @@ bool Rename(const Path &srcFilename, const Path &destFilename) { } // copies file srcFilename to destFilename, returns true on success -bool Copy(const Path &srcFilename, const Path &destFilename) -{ +bool Copy(const Path &srcFilename, const Path &destFilename) { switch (srcFilename.Type()) { case PathType::NATIVE: break; // OK case PathType::CONTENT_URI: - ERROR_LOG_REPORT_ONCE(copyUriNotSupported, COMMON, "Copying files by Android URI is not yet supported"); + if (destFilename.Type() == PathType::CONTENT_URI && destFilename.CanNavigateUp()) { + Path destParent = destFilename.NavigateUp(); + // Use native file copy. + if (Android_CopyFile(srcFilename.ToString(), destParent.ToString()) == StorageError::SUCCESS) { + return true; + } + // Else fall through, and try using file I/O. + } break; default: return false; } - switch (destFilename.Type()) { - case PathType::NATIVE: - break; // OK - case PathType::CONTENT_URI: - ERROR_LOG_REPORT_ONCE(copyUriNotSupported, COMMON, "Copying files by Android URI is not yet supported"); - return false; - default: - return false; - } INFO_LOG(COMMON, "Copy: %s --> %s", srcFilename.c_str(), destFilename.c_str()); #ifdef _WIN32 @@ -631,7 +641,7 @@ bool Copy(const Path &srcFilename, const Path &destFilename) #else // buffer size -#define BSIZE 4096 +#define BSIZE 16384 char buffer[BSIZE]; @@ -685,7 +695,21 @@ bool Copy(const Path &srcFilename, const Path &destFilename) #endif } +// Will overwrite the target. bool Move(const Path &srcFilename, const Path &destFilename) { + // Try a shortcut in Android Storage scenarios. + if (srcFilename.Type() == PathType::CONTENT_URI && destFilename.Type() == PathType::CONTENT_URI && srcFilename.CanNavigateUp() && destFilename.CanNavigateUp()) { + // We do not handle simultaneous renames here. + if (srcFilename.GetFilename() == destFilename.GetFilename()) { + Path srcParent = srcFilename.NavigateUp(); + Path dstParent = destFilename.NavigateUp(); + if (Android_MoveFile(srcFilename.ToString(), srcParent.ToString(), dstParent.ToString()) == StorageError::SUCCESS) { + return true; + } + // If failed, fall through and try other ways. + } + } + if (Rename(srcFilename, destFilename)) { return true; } else if (Copy(srcFilename, destFilename)) { diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index a45112599e..5f0e905dda 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -279,7 +279,6 @@ Path Path::GetRootVolume() const { return Path(path); } #endif - return Path("/"); } @@ -301,7 +300,7 @@ bool Path::IsAbsolute() const { return false; } -std::string Path::PathTo(const Path &other) { +std::string Path::PathTo(const Path &other) const { if (!other.StartsWith(*this)) { // Can't do this. Should return an error. return std::string(); diff --git a/Common/File/Path.h b/Common/File/Path.h index 3b0c911203..afec18ac7e 100644 --- a/Common/File/Path.h +++ b/Common/File/Path.h @@ -82,7 +82,7 @@ public: // For Android directory trees, navigates to the root of the tree. Path GetRootVolume() const; - std::string PathTo(const Path &child); + std::string PathTo(const Path &child) const; bool operator ==(const Path &other) const { return path_ == other.path_ && type_ == other.type_; diff --git a/Common/Serialize/Serializer.h b/Common/Serialize/Serializer.h index cfe14f01d8..77f024975c 100644 --- a/Common/Serialize/Serializer.h +++ b/Common/Serialize/Serializer.h @@ -143,7 +143,8 @@ public: if (p.error != p.ERROR_FAILURE) { return ERROR_NONE; } else { - *errorString = std::string("Failure at ") + p.GetBadSectionTitle(); + std::string badSectionTitle = p.GetBadSectionTitle() ? p.GetBadSectionTitle() : "(unknown bad section)"; + *errorString = std::string("Failure at ") + badSectionTitle; return ERROR_BROKEN_STATE; } } diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index dbb206185d..c3fc74ffdc 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -972,6 +972,7 @@ bool TextEdit::Key(const KeyInput &input) { } break; case NKCODE_ENTER: + case NKCODE_NUMPAD_ENTER: { EventParams e{}; e.v = this; diff --git a/Core/CwCheat.cpp b/Core/CwCheat.cpp index 0a348cf089..d1a12f98d6 100644 --- a/Core/CwCheat.cpp +++ b/Core/CwCheat.cpp @@ -98,6 +98,9 @@ bool CheatFileParser::Parse() { if (!tempLine) continue; + // Detect UTF-8 BOM sequence, and ignore it. + if (line_ == 1 && memcmp(tempLine, "\xEF\xBB\xBF", 3) == 0) + tempLine += 3; std::string line = TrimString(tempLine); // Minimum length 5 is shortest possible _ lines name of the game "_G N+" diff --git a/Core/FileLoaders/LocalFileLoader.cpp b/Core/FileLoaders/LocalFileLoader.cpp index d0c4c6828c..2e2caeff74 100644 --- a/Core/FileLoaders/LocalFileLoader.cpp +++ b/Core/FileLoaders/LocalFileLoader.cpp @@ -60,8 +60,8 @@ LocalFileLoader::LocalFileLoader(const Path &filename) #if PPSSPP_PLATFORM(ANDROID) if (filename.Type() == PathType::CONTENT_URI) { int fd = Android_OpenContentUriFd(filename.ToString(), Android_OpenContentUriMode::READ); - INFO_LOG(SYSTEM, "Fd %d for content URI: '%s'", fd, filename.c_str()); - if (fd == -1) { + VERBOSE_LOG(SYSTEM, "Fd %d for content URI: '%s'", fd, filename.c_str()); + if (fd < 0) { ERROR_LOG(FILESYS, "LoadFileLoader failed to open content URI: '%s'", filename.c_str()); return; } diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 645ef15df6..4e2ab9b2d8 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -177,17 +177,40 @@ DirectoryFileSystem::~DirectoryFileSystem() { CloseAll(); } -Path DirectoryFileHandle::GetLocalPath(const Path &basePath, std::string localpath) -{ +// TODO(scoped): Merge the two below functions somehow. + +Path DirectoryFileHandle::GetLocalPath(const Path &basePath, std::string localpath) const { if (localpath.empty()) return basePath; if (localpath[0] == '/') localpath.erase(0, 1); + if (fileSystemFlags_ & FileSystemFlags::STRIP_PSP) { + if (startsWith(localpath, "PSP/")) { + localpath = localpath.substr(4); + } + } + return basePath / localpath; } +Path DirectoryFileSystem::GetLocalPath(std::string internalPath) const { + if (internalPath.empty()) + return basePath; + + if (internalPath[0] == '/') + internalPath.erase(0, 1); + + if (flags & FileSystemFlags::STRIP_PSP) { + if (startsWith(internalPath, "PSP/")) { + internalPath = internalPath.substr(4); + } + } + + return basePath / internalPath; +} + bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, FileAccess access, u32 &error) { error = 0; @@ -203,7 +226,6 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File #endif Path fullName = GetLocalPath(basePath, fileName); - INFO_LOG(FILESYS, "Actually opening %s", fullName.c_str()); // On the PSP, truncating doesn't lose data. If you seek later, you'll recover it. // This is abnormal, so we deviate from the PSP's behavior and truncate on write/close. @@ -302,8 +324,6 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File } } - INFO_LOG(FILESYS, "Opening '%s' straight", fullName.c_str()); - int flags = 0; if (access & FILEACCESS_APPEND) { flags |= O_APPEND; @@ -511,16 +531,6 @@ void DirectoryFileSystem::CloseAll() { entries.clear(); } -Path DirectoryFileSystem::GetLocalPath(std::string internalPath) { - if (internalPath.empty()) - return basePath; - - if (internalPath[0] == '/') - internalPath.erase(0, 1); - - return basePath / internalPath; -} - bool DirectoryFileSystem::MkDir(const std::string &dirname) { bool result; #if HOST_IS_CASE_SENSITIVE @@ -628,6 +638,7 @@ bool DirectoryFileSystem::RemoveFile(const std::string &filename) { int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) { OpenFileEntry entry; + entry.hFile.fileSystemFlags_ = flags; u32 err = 0; bool success = entry.hFile.Open(basePath, filename, access, err); if (err == 0 && !success) { @@ -954,6 +965,7 @@ void DirectoryFileSystem::DoState(PointerWrap &p) { CloseAll(); u32 key; OpenFileEntry entry; + entry.hFile.fileSystemFlags_ = flags; for (u32 i = 0; i < num; i++) { Do(p, key); Do(p, entry.guestFilename); diff --git a/Core/FileSystems/DirectoryFileSystem.h b/Core/FileSystems/DirectoryFileSystem.h index b7b71c7c34..9d8478d691 100644 --- a/Core/FileSystems/DirectoryFileSystem.h +++ b/Core/FileSystems/DirectoryFileSystem.h @@ -72,11 +72,14 @@ struct DirectoryFileHandle { s64 needsTrunc_ = -1; bool replay_ = true; bool inGameDir_ = false; + FileSystemFlags fileSystemFlags_ = (FileSystemFlags)0; - DirectoryFileHandle(Flags flags) : replay_(flags != SKIP_REPLAY) { - } + DirectoryFileHandle() {} - Path GetLocalPath(const Path &basePath, std::string localpath); + DirectoryFileHandle(Flags flags, FileSystemFlags fileSystemFlags) + : replay_(flags != SKIP_REPLAY), fileSystemFlags_(fileSystemFlags) {} + + Path GetLocalPath(const Path &basePath, std::string localpath) const; bool Open(const Path &basePath, std::string &fileName, FileAccess access, u32 &err); size_t Read(u8* pointer, s64 size); size_t Write(const u8* pointer, s64 size); @@ -114,7 +117,7 @@ public: private: struct OpenFileEntry { - DirectoryFileHandle hFile = DirectoryFileHandle::NORMAL; + DirectoryFileHandle hFile; std::string guestFilename; FileAccess access = FILEACCESS_NONE; }; @@ -124,8 +127,8 @@ private: Path basePath; IHandleAllocator *hAlloc; FileSystemFlags flags; - // In case of Windows: Translate slashes, etc. - Path GetLocalPath(std::string internalPath); + + Path GetLocalPath(std::string internalPath) const; }; // VFSFileSystem: Ability to map in Android APK paths as well! Does not support all features, only meant for fonts. diff --git a/Core/FileSystems/FileSystem.h b/Core/FileSystems/FileSystem.h index 87e8c92e07..5df227dd1f 100644 --- a/Core/FileSystems/FileSystem.h +++ b/Core/FileSystems/FileSystem.h @@ -63,6 +63,7 @@ enum class FileSystemFlags { UMD = 2, CARD = 4, FLASH = 8, + STRIP_PSP = 16, }; ENUM_CLASS_BITOPS(FileSystemFlags); diff --git a/Core/FileSystems/ISOFileSystem.h b/Core/FileSystems/ISOFileSystem.h index 676964f079..0b8c00512a 100644 --- a/Core/FileSystems/ISOFileSystem.h +++ b/Core/FileSystems/ISOFileSystem.h @@ -19,6 +19,7 @@ #include #include +#include #include "FileSystem.h" @@ -100,7 +101,7 @@ private: // the filenames to "", to achieve this. class ISOBlockSystem : public IFileSystem { public: - ISOBlockSystem(ISOFileSystem *isoFileSystem) : isoFileSystem_(isoFileSystem) {} + ISOBlockSystem(std::shared_ptr isoFileSystem) : isoFileSystem_(isoFileSystem) {} void DoState(PointerWrap &p) override { // This is a bit iffy, as block device savestates already are iffy (loads/saves multiple times for multiple mounts..) @@ -150,5 +151,5 @@ public: bool RemoveFile(const std::string &filename) override { return false; } private: - ISOFileSystem *isoFileSystem_; + std::shared_ptr isoFileSystem_; }; diff --git a/Core/FileSystems/MetaFileSystem.cpp b/Core/FileSystems/MetaFileSystem.cpp index 662d99bad8..6ecd36226c 100644 --- a/Core/FileSystems/MetaFileSystem.cpp +++ b/Core/FileSystems/MetaFileSystem.cpp @@ -172,10 +172,11 @@ IFileSystem *MetaFileSystem::GetHandleOwner(u32 handle) for (size_t i = 0; i < fileSystems.size(); i++) { if (fileSystems[i].system->OwnsHandle(handle)) - return fileSystems[i].system; //got it! + return fileSystems[i].system.get(); } - //none found? - return 0; + + // Not found + return nullptr; } int MetaFileSystem::MapFilePath(const std::string &_inpath, std::string &outpath, MountPoint **system) @@ -274,41 +275,47 @@ std::string MetaFileSystem::NormalizePrefix(std::string prefix) const { return prefix; } -void MetaFileSystem::Mount(std::string prefix, IFileSystem *system) { +void MetaFileSystem::Mount(std::string prefix, std::shared_ptr system) { std::lock_guard guard(lock); + MountPoint x; x.prefix = prefix; x.system = system; + for (auto &it : fileSystems) { + if (it.prefix == prefix) { + // Overwrite the old mount. Don't create a new one. + it = x; + return; + } + } + + // Prefix not yet mounted, do so. fileSystems.push_back(x); } -void MetaFileSystem::Unmount(std::string prefix, IFileSystem *system) { - std::lock_guard guard(lock); - MountPoint x; - x.prefix = prefix; - x.system = system; - fileSystems.erase(std::remove(fileSystems.begin(), fileSystems.end(), x), fileSystems.end()); +void MetaFileSystem::UnmountAll() { + fileSystems.clear(); + currentDir.clear(); } -void MetaFileSystem::Remount(std::string prefix, IFileSystem *newSystem) { +void MetaFileSystem::Unmount(std::string prefix) { + for (auto iter = fileSystems.begin(); iter != fileSystems.end(); iter++) { + if (iter->prefix == prefix) { + fileSystems.erase(iter); + return; + } + } +} + +bool MetaFileSystem::Remount(std::string prefix, std::shared_ptr system) { std::lock_guard guard(lock); - IFileSystem *oldSystem = nullptr; for (auto &it : fileSystems) { if (it.prefix == prefix) { - oldSystem = it.system; - it.system = newSystem; + it.system = system; + return true; } } - - bool delOldSystem = true; - for (auto &it : fileSystems) { - if (it.system == oldSystem) { - delOldSystem = false; - } - } - - if (delOldSystem) - delete oldSystem; + return false; } IFileSystem *MetaFileSystem::GetSystemFromFilename(const std::string &filename) { @@ -321,31 +328,16 @@ IFileSystem *MetaFileSystem::GetSystemFromFilename(const std::string &filename) IFileSystem *MetaFileSystem::GetSystem(const std::string &prefix) { for (auto it = fileSystems.begin(); it != fileSystems.end(); ++it) { if (it->prefix == NormalizePrefix(prefix)) - return it->system; + return it->system.get(); } return NULL; } -void MetaFileSystem::Shutdown() -{ +void MetaFileSystem::Shutdown() { std::lock_guard guard(lock); - current = 6; - // Ownership is a bit convoluted. Let's just delete everything once. - - std::set toDelete; - for (size_t i = 0; i < fileSystems.size(); i++) { - toDelete.insert(fileSystems[i].system); - } - - for (auto iter = toDelete.begin(); iter != toDelete.end(); ++iter) - { - delete *iter; - } - - fileSystems.clear(); - currentDir.clear(); - startingDirectory = ""; + UnmountAll(); + Reset(); } int MetaFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) diff --git a/Core/FileSystems/MetaFileSystem.h b/Core/FileSystems/MetaFileSystem.h index a1152e1abd..f980fc1900 100644 --- a/Core/FileSystems/MetaFileSystem.h +++ b/Core/FileSystems/MetaFileSystem.h @@ -20,6 +20,7 @@ #include #include #include +#include #include "Core/FileSystems/FileSystem.h" @@ -28,13 +29,14 @@ private: s32 current; struct MountPoint { std::string prefix; - IFileSystem *system; + std::shared_ptr system; bool operator == (const MountPoint &other) const { return prefix == other.prefix && system == other.system; } }; + // The order of this vector is meaningful - lookups are always a linear search from the start. std::vector fileSystems; typedef std::map currentDir_t; @@ -43,26 +45,35 @@ private: std::string startingDirectory; std::recursive_mutex lock; // must be recursive -public: - MetaFileSystem() { + void Reset() { // This used to be 6, probably an attempt to replicate PSP handles. // However, that's an artifact of using psplink anyway... current = 1; + startingDirectory.clear(); } - void Mount(std::string prefix, IFileSystem *system); - void Unmount(std::string prefix, IFileSystem *system); - void Remount(std::string prefix, IFileSystem *newSystem); +public: + MetaFileSystem() { + Reset(); + } + void Mount(std::string prefix, std::shared_ptr system); + // Fails if there's not already a file system at prefix. + bool Remount(std::string prefix, std::shared_ptr system); + + void UnmountAll(); + void Unmount(std::string prefix); + + // The pointer returned from these are for temporary usage only. Do not store. IFileSystem *GetSystem(const std::string &prefix); IFileSystem *GetSystemFromFilename(const std::string &filename); + IFileSystem *GetHandleOwner(u32 handle); FileSystemFlags FlagsFromFilename(const std::string &filename) { IFileSystem *sys = GetSystemFromFilename(filename); return sys ? sys->Flags() : FileSystemFlags::NONE; } void ThreadEnded(int threadID); - void Shutdown(); u32 GetNewHandle() override { @@ -77,14 +88,13 @@ public: void DoState(PointerWrap &p) override; - IFileSystem *GetHandleOwner(u32 handle); int MapFilePath(const std::string &inpath, std::string &outpath, MountPoint **system); inline int MapFilePath(const std::string &_inpath, std::string &outpath, IFileSystem **system) { MountPoint *mountPoint; int error = MapFilePath(_inpath, outpath, &mountPoint); if (error == 0) { - *system = mountPoint->system; + *system = mountPoint->system.get(); return error; } diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index 276a89f1be..889bc506fc 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -187,7 +187,7 @@ void VirtualDiscFileSystem::DoState(PointerWrap &p) for (int i = 0; i < entryCount; i++) { u32 fd = 0; - OpenFileEntry of; + OpenFileEntry of(Flags()); Do(p, fd); Do(p, of.fileIndex); @@ -214,6 +214,7 @@ void VirtualDiscFileSystem::DoState(PointerWrap &p) } } + // TODO: I think we only need to write to the map on load? entries[fd] = of; } } else { @@ -321,7 +322,7 @@ int VirtualDiscFileSystem::getFileListIndex(u32 accessBlock, u32 accessSize, boo int VirtualDiscFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) { - OpenFileEntry entry; + OpenFileEntry entry(Flags()); entry.curOffset = 0; entry.size = 0; entry.startOffset = 0; @@ -471,7 +472,7 @@ size_t VirtualDiscFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &u return 0; } - OpenFileEntry temp; + OpenFileEntry temp(Flags()); if (fileList[fileIndex].handler != NULL) { temp.handler = fileList[fileIndex].handler; } diff --git a/Core/FileSystems/VirtualDiscFileSystem.h b/Core/FileSystems/VirtualDiscFileSystem.h index bd8604ae3d..6c76d6b6fb 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.h +++ b/Core/FileSystems/VirtualDiscFileSystem.h @@ -87,17 +87,15 @@ private: ReadFunc Read; CloseFunc Close; - bool IsValid() const { return library != NULL; } + bool IsValid() const { return library != nullptr; } }; struct HandlerFileHandle { Handler *handler; HandlerHandle handle; - HandlerFileHandle() : handler(NULL), handle(0) { - } - HandlerFileHandle(Handler *handler_) : handler(handler_), handle(-1) { - } + HandlerFileHandle() : handler(nullptr), handle(0) {} + HandlerFileHandle(Handler *handler_) : handler(handler_), handle(-1) {} bool Open(const std::string& basePath, const std::string& fileName, FileAccess access) { // Ignore access, read only. @@ -115,7 +113,7 @@ private: } bool IsValid() { - return handler != NULL && handler->IsValid(); + return handler != nullptr && handler->IsValid(); } HandlerFileHandle &operator =(Handler *_handler) { @@ -127,13 +125,18 @@ private: typedef enum { VFILETYPE_NORMAL, VFILETYPE_LBN, VFILETYPE_ISO } VirtualFileType; struct OpenFileEntry { - DirectoryFileHandle hFile = DirectoryFileHandle::SKIP_REPLAY; + OpenFileEntry() {} + OpenFileEntry(FileSystemFlags fileSystemFlags) { + hFile = DirectoryFileHandle(DirectoryFileHandle::SKIP_REPLAY, fileSystemFlags); + } + + DirectoryFileHandle hFile; HandlerFileHandle handler; - VirtualFileType type; - u32 fileIndex; - u64 curOffset; - u64 startOffset; // only used by lbn files - u64 size; // only used by lbn files + VirtualFileType type = VFILETYPE_NORMAL; + u32 fileIndex = 0; + u64 curOffset = 0; + u64 startOffset = 0; // only used by lbn files + u64 size = 0; // only used by lbn files bool Open(const Path &basePath, std::string& fileName, FileAccess access) { // Ignored, we're read only. @@ -168,6 +171,7 @@ private: }; typedef std::map EntryMap; + EntryMap entries; IHandleAllocator *hAlloc; Path basePath; diff --git a/Core/HLE/proAdhoc.cpp b/Core/HLE/proAdhoc.cpp index c99c22590e..7a7033b91d 100644 --- a/Core/HLE/proAdhoc.cpp +++ b/Core/HLE/proAdhoc.cpp @@ -1591,8 +1591,9 @@ int friendFinder(){ } // Update HUD User Count + name = (char*)packet->name.data; incoming = ""; - incoming.append((char*)packet->name.data); + incoming.append(name.substr(0, 8)); incoming.append(" Joined "); //do we need ip? //joined.append((char *)packet->ip); diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 73702ae4e3..c856794110 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "Common/Thread/ThreadUtil.h" #include "Common/Profiler/Profiler.h" @@ -557,7 +558,6 @@ static void __IoAsyncEndCallback(SceUID threadID, SceUID prevCallbackId) { } } -static DirectoryFileSystem *memstickSystem = nullptr; static DirectoryFileSystem *exdataSystem = nullptr; #if defined(USING_WIN_UI) || defined(APPLE) static DirectoryFileSystem *flash0System = nullptr; @@ -631,23 +631,35 @@ void __IoInit() { asyncNotifyEvent = CoreTiming::RegisterEvent("IoAsyncNotify", __IoAsyncNotify); syncNotifyEvent = CoreTiming::RegisterEvent("IoSyncNotify", __IoSyncNotify); - memstickSystem = new DirectoryFileSystem(&pspFileSystem, g_Config.memStickDirectory, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD); + // TODO(scoped): This won't work if memStickDirectory points at the contents of /PSP... #if defined(USING_WIN_UI) || defined(APPLE) - flash0System = new DirectoryFileSystem(&pspFileSystem, g_Config.flash0Directory, FileSystemFlags::FLASH); + auto flash0System = std::shared_ptr(new DirectoryFileSystem(&pspFileSystem, g_Config.flash0Directory, FileSystemFlags::FLASH)); #else - flash0System = new VFSFileSystem(&pspFileSystem, "flash0"); + auto flash0System = std::shared_ptr(new VFSFileSystem(&pspFileSystem, "flash0")); #endif + FileSystemFlags memstickFlags = FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD; + + Path pspDir = GetSysDirectory(DIRECTORY_PSP); + if (pspDir == g_Config.memStickDirectory) { + // Initially tried to do this with dual mounts, but failed due to save state compatibility issues. + INFO_LOG(SCEIO, "Enabling /PSP compatibility mode"); + memstickFlags |= FileSystemFlags::STRIP_PSP; + } + + auto memstickSystem = std::shared_ptr(new DirectoryFileSystem(&pspFileSystem, g_Config.memStickDirectory, memstickFlags)); + pspFileSystem.Mount("ms0:", memstickSystem); pspFileSystem.Mount("fatms0:", memstickSystem); pspFileSystem.Mount("fatms:", memstickSystem); pspFileSystem.Mount("pfat0:", memstickSystem); + pspFileSystem.Mount("flash0:", flash0System); if (g_RemasterMode) { const std::string gameId = g_paramSFO.GetDiscID(); const Path exdataPath = GetSysDirectory(DIRECTORY_EXDATA) / gameId; if (File::Exists(exdataPath)) { - exdataSystem = new DirectoryFileSystem(&pspFileSystem, exdataPath, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD); + auto exdataSystem = std::shared_ptr(new DirectoryFileSystem(&pspFileSystem, exdataPath, FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD)); pspFileSystem.Mount("exdata0:", exdataSystem); INFO_LOG(SCEIO, "Mounted exdata/%s/ under memstick for exdata0:/", gameId.c_str()); } else { @@ -763,22 +775,12 @@ void __IoShutdown() { } asyncDefaultPriority = -1; - pspFileSystem.Unmount("ms0:", memstickSystem); - pspFileSystem.Unmount("fatms0:", memstickSystem); - pspFileSystem.Unmount("fatms:", memstickSystem); - pspFileSystem.Unmount("pfat0:", memstickSystem); - pspFileSystem.Unmount("flash0:", flash0System); - - if (g_RemasterMode && exdataSystem) { - pspFileSystem.Unmount("exdata0:", exdataSystem); - delete exdataSystem; - exdataSystem = nullptr; - } - - delete memstickSystem; - memstickSystem = nullptr; - delete flash0System; - flash0System = nullptr; + pspFileSystem.Unmount("ms0:"); + pspFileSystem.Unmount("fatms0:"); + pspFileSystem.Unmount("fatms:"); + pspFileSystem.Unmount("pfat0:"); + pspFileSystem.Unmount("flash0:"); + pspFileSystem.Unmount("exdata0:"); MemoryStick_Shutdown(); memStickCallbacks.clear(); diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 869c6f8492..73a6a8e746 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -80,11 +80,11 @@ void InitMemoryForGameISO(FileLoader *fileLoader) { return; } - IFileSystem *fileSystem = nullptr; - IFileSystem *blockSystem = nullptr; + std::shared_ptr fileSystem; + std::shared_ptr blockSystem; if (fileLoader->IsDirectory()) { - fileSystem = new VirtualDiscFileSystem(&pspFileSystem, fileLoader->GetPath()); + fileSystem = std::shared_ptr(new VirtualDiscFileSystem(&pspFileSystem, fileLoader->GetPath())); blockSystem = fileSystem; } else { auto bd = constructBlockDevice(fileLoader); @@ -92,9 +92,9 @@ void InitMemoryForGameISO(FileLoader *fileLoader) { if (!bd) return; - ISOFileSystem *iso = new ISOFileSystem(&pspFileSystem, bd); + std::shared_ptr iso = std::shared_ptr(new ISOFileSystem(&pspFileSystem, bd)); fileSystem = iso; - blockSystem = new ISOBlockSystem(iso); + blockSystem = std::shared_ptr(new ISOBlockSystem(iso)); } pspFileSystem.Mount("umd0:", blockSystem); @@ -148,20 +148,20 @@ bool ReInitMemoryForGameISO(FileLoader *fileLoader) { return false; } - IFileSystem *fileSystem = nullptr; - IFileSystem *blockSystem = nullptr; + std::shared_ptr fileSystem; + std::shared_ptr blockSystem; if (fileLoader->IsDirectory()) { - fileSystem = new VirtualDiscFileSystem(&pspFileSystem, fileLoader->GetPath()); + fileSystem = std::shared_ptr(new VirtualDiscFileSystem(&pspFileSystem, fileLoader->GetPath())); blockSystem = fileSystem; } else { auto bd = constructBlockDevice(fileLoader); if (!bd) return false; - ISOFileSystem *iso = new ISOFileSystem(&pspFileSystem, bd); + std::shared_ptr iso = std::shared_ptr(new ISOFileSystem(&pspFileSystem, bd)); fileSystem = iso; - blockSystem = new ISOBlockSystem(iso); + blockSystem = std::shared_ptr(new ISOBlockSystem(iso)); } pspFileSystem.Remount("umd0:", blockSystem); @@ -367,14 +367,15 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { if (PSP_CoreParameter().mountIsoLoader != nullptr) { auto bd = constructBlockDevice(PSP_CoreParameter().mountIsoLoader); if (bd != NULL) { - ISOFileSystem *umd2 = new ISOFileSystem(&pspFileSystem, bd); - ISOBlockSystem *blockSystem = new ISOBlockSystem(umd2); + std::shared_ptr umd2 = std::shared_ptr(new ISOFileSystem(&pspFileSystem, bd)); + std::shared_ptr blockSystem = std::shared_ptr(new ISOBlockSystem(umd2)); pspFileSystem.Mount("umd1:", blockSystem); pspFileSystem.Mount("disc0:", umd2); pspFileSystem.Mount("umd:", blockSystem); } } + Path full_path = fileLoader->GetPath(); std::string path = full_path.GetDirectory(); std::string extension = full_path.GetFileExtension(); @@ -411,7 +412,7 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { pspFileSystem.SetStartingDirectory(ms_path); } - DirectoryFileSystem *fs = new DirectoryFileSystem(&pspFileSystem, Path(path), FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD); + std::shared_ptr fs = std::shared_ptr(new DirectoryFileSystem(&pspFileSystem, Path(path), FileSystemFlags::SIMULATE_FAT32 | FileSystemFlags::CARD)); pspFileSystem.Mount("umd0:", fs); std::string finalName = ms_path + file; @@ -469,7 +470,7 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { } bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { - BlobFileSystem *umd = new BlobFileSystem(&pspFileSystem, fileLoader, "data.ppdmp"); + std::shared_ptr umd = std::shared_ptr(new BlobFileSystem(&pspFileSystem, fileLoader, "data.ppdmp")); pspFileSystem.Mount("disc0:", umd); PSPLoaders_Shutdown(); diff --git a/Core/System.cpp b/Core/System.cpp index aace6ccfa7..d11fb469fe 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -351,6 +351,7 @@ void CPU_Shutdown() { if (coreParameter.enableSound) { Audio_Shutdown(); } + pspFileSystem.Shutdown(); mipsr4k.Shutdown(); Memory::Shutdown(); @@ -591,6 +592,8 @@ Path GetSysDirectory(PSPDirectories directoryType) { } switch (directoryType) { + case DIRECTORY_PSP: + return pspDirectory; case DIRECTORY_CHEATS: return pspDirectory / "Cheats"; case DIRECTORY_GAME: @@ -705,8 +708,8 @@ void InitSysDirectories() { // 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. - File::CreateDir(g_Config.memStickDirectory / "PSP"); - File::CreateDir(g_Config.memStickDirectory / "PSP/COMMON"); + File::CreateDir(GetSysDirectory(DIRECTORY_PSP)); + File::CreateDir(GetSysDirectory(DIRECTORY_PSP) / "COMMON"); File::CreateDir(GetSysDirectory(DIRECTORY_GAME)); File::CreateDir(GetSysDirectory(DIRECTORY_SAVEDATA)); File::CreateDir(GetSysDirectory(DIRECTORY_SAVESTATE)); diff --git a/Core/System.h b/Core/System.h index 29b95bb7a4..13423a0167 100644 --- a/Core/System.h +++ b/Core/System.h @@ -37,6 +37,7 @@ enum GlobalUIState { // Use these in conjunction with GetSysDirectory. enum PSPDirectories { + DIRECTORY_PSP, DIRECTORY_CHEATS, DIRECTORY_SCREENSHOT, DIRECTORY_SYSTEM, diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 8596294209..6f7872289a 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -540,10 +540,15 @@ UI::EventReturn GameBrowser::StorageClick(UI::EventParams &e) { return UI::EVENT_DONE; } -UI::EventReturn GameBrowser::HomeClick(UI::EventParams &e) { - if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { - if (path_.GetPath().Type() == PathType::CONTENT_URI) { - path_.SetPath(path_.GetPath().GetRootVolume()); +UI::EventReturn GameBrowser::OnHomeClick(UI::EventParams &e) { + if (path_.GetPath().Type() == PathType::CONTENT_URI) { + Path rootPath = path_.GetPath().GetRootVolume(); + if (rootPath != path_.GetPath()) { + SetPath(rootPath); + return UI::EVENT_DONE; + } + if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { + // There'll be no sensible home, ignore. return UI::EVENT_DONE; } } @@ -669,7 +674,7 @@ void GameBrowser::Refresh() { if (browseFlags_ & BrowseFlags::NAVIGATE) { topBar->Add(new Spacer(2.0f)); topBar->Add(new TextView(path_.GetFriendlyPath().c_str(), ALIGN_VCENTER | FLAG_WRAP_TEXT, true, new LinearLayoutParams(FILL_PARENT, 64.0f, 1.0f))); - topBar->Add(new Choice(ImageID("I_HOME"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::HomeClick); + topBar->Add(new Choice(ImageID("I_HOME"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::OnHomeClick); if (System_GetPropertyBool(SYSPROP_HAS_ADDITIONAL_STORAGE)) { topBar->Add(new Choice(ImageID("I_SDCARD"), new LayoutParams(WRAP_CONTENT, 64.0f)))->OnClick.Handle(this, &GameBrowser::StorageClick); } diff --git a/UI/MainScreen.h b/UI/MainScreen.h index 9a19321755..6b15f06427 100644 --- a/UI/MainScreen.h +++ b/UI/MainScreen.h @@ -74,7 +74,7 @@ private: UI::EventReturn LastClick(UI::EventParams &e); UI::EventReturn BrowseClick(UI::EventParams &e); UI::EventReturn StorageClick(UI::EventParams &e); - UI::EventReturn HomeClick(UI::EventParams &e); + UI::EventReturn OnHomeClick(UI::EventParams &e); UI::EventReturn PinToggleClick(UI::EventParams &e); UI::EventReturn GridSettingsClick(UI::EventParams &e); UI::EventReturn OnRecentClear(UI::EventParams &e); diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index e91ec0d58b..3349afd9e6 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -386,6 +386,7 @@ + @@ -518,6 +519,7 @@ + @@ -640,4 +642,4 @@ - + \ No newline at end of file diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index a72807393e..740747dfd4 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -375,6 +375,9 @@ GPU + + File + @@ -691,6 +694,9 @@ GPU + + File + @@ -705,4 +711,4 @@ Math\lin - + \ No newline at end of file diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 7d7ad42489..761cc156ab 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -8,8 +8,13 @@ import android.os.Bundle; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.util.Log; +import android.system.StructStatVfs; +import android.system.Os; import android.os.storage.StorageManager; +import android.content.ContentResolver; +import android.database.Cursor; import android.provider.DocumentsContract; +import android.os.Environment; import androidx.documentfile.provider.DocumentFile; import java.util.ArrayList; import java.util.UUID; @@ -27,6 +32,13 @@ public class PpssppActivity extends NativeActivity { public static boolean libraryLoaded = false; + // Matches the enum in AndroidStorage.h. + private static final int STORAGE_ERROR_SUCCESS = 0; + private static final int STORAGE_ERROR_UNKNOWN = -1; + private static final int STORAGE_ERROR_NOT_FOUND = -2; + private static final int STORAGE_ERROR_DISK_FULL = -3; + private static final int STORAGE_ERROR_ALREADY_EXISTS = -4; + @SuppressWarnings("deprecation") public static void CheckABIAndLoadLibrary() { if (Build.CPU_ABI.equals("armeabi")) { @@ -135,167 +147,234 @@ public class PpssppActivity extends NativeActivity { } } - private static String fileInfoToString(DocumentFile file) { + private static final String[] columns = new String[] { + DocumentsContract.Document.COLUMN_DISPLAY_NAME, + DocumentsContract.Document.COLUMN_SIZE, + DocumentsContract.Document.COLUMN_FLAGS, + DocumentsContract.Document.COLUMN_MIME_TYPE, // check for MIME_TYPE_DIR + DocumentsContract.Document.COLUMN_LAST_MODIFIED + }; + + private String cursorToString(Cursor c) { + final int flags = c.getInt(2); + // Filter out any virtual or partial nonsense. + // There's a bunch of potentially-interesting flags here btw, + // to figure out how to set access flags better, etc. + if ((flags & (DocumentsContract.Document.FLAG_PARTIAL | DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT)) != 0) { + return null; + } + + final String mimeType = c.getString(3); + final boolean isDirectory = mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR); + final String documentName = c.getString(0); + final long size = c.getLong(1); + final long lastModified = c.getLong(4); + String str = "F|"; - if (file.isVirtual()) { - // This we don't want to see. - str = "V|"; - Log.e(TAG, "Got virtual file: " + file.getUri()); - } else if (file.isDirectory()) { + if (isDirectory) { str = "D|"; } - str += file.length() + "|" + file.getName() + "|" + file.getUri() + "|" + file.lastModified(); - return str; + return str + size + "|" + documentName + "|" + lastModified; } // TODO: Maybe add a cheaper version that doesn't extract all the file information? // TODO: Replace with a proper query: // * https://stackoverflow.com/questions/42186820/documentfile-is-very-slow public String[] listContentUriDir(String uriString) { + Cursor c = null; try { Uri uri = Uri.parse(uriString); - DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri); - DocumentFile[] children = documentFile.listFiles(); - ArrayList listing = new ArrayList(); - // Encode entries into strings for JNI simplicity. - for (DocumentFile file : children) { - String str = fileInfoToString(file); - listing.add(str); + final ContentResolver resolver = getContentResolver(); + final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( + uri, DocumentsContract.getDocumentId(uri)); + final ArrayList listing = new ArrayList<>(); + c = resolver.query(childrenUri, columns, null, null, null); + while (c.moveToNext()) { + String str = cursorToString(c); + if (str != null) { + listing.add(str); + } } // Is ArrayList weird or what? String[] strings = new String[listing.size()]; return listing.toArray(strings); - } catch (Exception e) { + } + catch (IllegalArgumentException e) { + // Due to sloppy exception handling in resolver.query, we get this wrapping + // a FileNotFoundException if the directory doesn't exist. + return new String[]{}; + } + catch (Exception e) { Log.e(TAG, "listContentUriDir exception: " + e.toString()); return new String[]{}; + } finally { + if (c != null) { + c.close(); + } } } - public boolean contentUriCreateDirectory(String rootTreeUri, String dirName) { + public int contentUriCreateDirectory(String rootTreeUri, String dirName) { try { Uri uri = Uri.parse(rootTreeUri); DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri); if (documentFile != null) { DocumentFile createdDir = documentFile.createDirectory(dirName); - return createdDir != null; + return createdDir != null ? STORAGE_ERROR_SUCCESS : STORAGE_ERROR_UNKNOWN; } else { Log.e(TAG, "contentUriCreateDirectory: fromTreeUri returned null"); - return false; + return STORAGE_ERROR_UNKNOWN; } } catch (Exception e) { Log.e(TAG, "contentUriCreateDirectory exception: " + e.toString()); - return false; + return STORAGE_ERROR_UNKNOWN; } } - public boolean contentUriCreateFile(String rootTreeUri, String fileName) { + public int contentUriCreateFile(String rootTreeUri, String fileName) { try { Uri uri = Uri.parse(rootTreeUri); DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri); if (documentFile != null) { // TODO: Check the file extension and choose MIME type appropriately. DocumentFile createdFile = documentFile.createFile("application/octet-stream", fileName); - return createdFile != null; + return createdFile != null ? STORAGE_ERROR_SUCCESS : STORAGE_ERROR_UNKNOWN; } else { Log.e(TAG, "contentUriCreateFile: fromTreeUri returned null"); - return false; + return STORAGE_ERROR_UNKNOWN; } } catch (Exception e) { Log.e(TAG, "contentUriCreateFile exception: " + e.toString()); - return false; + return STORAGE_ERROR_UNKNOWN; } } - public boolean contentUriRemoveFile(String fileName) { + public int contentUriRemoveFile(String fileName) { try { Uri uri = Uri.parse(fileName); DocumentFile documentFile = DocumentFile.fromSingleUri(this, uri); if (documentFile != null) { - return documentFile.delete(); + return documentFile.delete() ? STORAGE_ERROR_SUCCESS : STORAGE_ERROR_UNKNOWN; } else { - return false; + return STORAGE_ERROR_UNKNOWN; } } catch (Exception e) { Log.e(TAG, "contentUriRemoveFile exception: " + e.toString()); - return false; + return STORAGE_ERROR_UNKNOWN; } } - public boolean contentUriRenameFileTo(String fileUri, String newName) { + // NOTE: The destination is the parent directory! This means that contentUriCopyFile + // cannot rename things as part of the operation. + public int contentUriCopyFile(String srcFileUri, String dstParentDirUri) { + try { + Uri srcUri = Uri.parse(srcFileUri); + Uri dstParentUri = Uri.parse(dstParentDirUri); + return DocumentsContract.copyDocument(getContentResolver(), srcUri, dstParentUri) != null ? STORAGE_ERROR_SUCCESS : STORAGE_ERROR_UNKNOWN; + } catch (Exception e) { + Log.e(TAG, "contentUriCopyFile exception: " + e.toString()); + return STORAGE_ERROR_UNKNOWN; + } + } + + // NOTE: The destination is the parent directory! This means that contentUriCopyFile + // cannot rename things as part of the operation. + public int contentUriMoveFile(String srcFileUri, String srcParentDirUri, String dstParentDirUri) { + try { + Uri srcUri = Uri.parse(srcFileUri); + Uri srcParentUri = Uri.parse(srcParentDirUri); + Uri dstParentUri = Uri.parse(dstParentDirUri); + return DocumentsContract.moveDocument(getContentResolver(), srcUri, srcParentUri, dstParentUri) != null ? STORAGE_ERROR_SUCCESS : STORAGE_ERROR_UNKNOWN; + } catch (Exception e) { + Log.e(TAG, "contentUriMoveFile exception: " + e.toString()); + return STORAGE_ERROR_UNKNOWN; + } + } + + public int contentUriRenameFileTo(String fileUri, String newName) { try { Uri uri = Uri.parse(fileUri); - // Due to a design flaw, we can't use DocumentFile.renameTo(). // Instead we use the DocumentsContract API directly. // See https://stackoverflow.com/questions/37168200/android-5-0-new-sd-card-access-api-documentfile-renameto-unsupportedoperation. Uri newUri = DocumentsContract.renameDocument(getContentResolver(), uri, newName); - // Log.i(TAG, "New uri: " + newUri.toString()); - return true; + return STORAGE_ERROR_SUCCESS; } catch (Exception e) { + // TODO: More detailed exception processing. Log.e(TAG, "contentUriRenameFile exception: " + e.toString()); - return false; + return STORAGE_ERROR_UNKNOWN; } } - // Possibly faster than contentUriGetFileInfo. + private static void closeQuietly(AutoCloseable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + + // Probably slightly faster than contentUriGetFileInfo. + // Smaller difference now than before I changed that one to a query... public boolean contentUriFileExists(String fileUri) { + Cursor c = null; try { Uri uri = Uri.parse(fileUri); - DocumentFile documentFile = DocumentFile.fromSingleUri(this, uri); - if (documentFile != null) { - if (documentFile.exists()) { - return true; - } else { - return false; - } - } else { - return false; - } + c = getContentResolver().query(uri, new String[] { DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null); + return c.getCount() > 0; } catch (Exception e) { - Log.e(TAG, "contentUriFileExists exception: " + e.toString()); + // Log.w(TAG, "Failed query: " + e); return false; + } finally { + closeQuietly(c); } } public String contentUriGetFileInfo(String fileName) { + Cursor c = null; try { Uri uri = Uri.parse(fileName); - DocumentFile documentFile = DocumentFile.fromSingleUri(this, uri); - if (documentFile != null) { - if (documentFile.exists()) { - String str = fileInfoToString(documentFile); - return str; - } else { - return null; - } + final ContentResolver resolver = getContentResolver(); + c = resolver.query(uri, columns, null, null, null); + if (c.moveToNext()) { + String str = cursorToString(c); + return str; } else { return null; } } catch (Exception e) { Log.e(TAG, "contentUriGetFileInfo exception: " + e.toString()); return null; + } finally { + if (c != null) { + c.close(); + } } } // The example in Android documentation uses this.getFilesDir for path. // There's also a way to beg the OS for more space, which might clear caches, but // let's just not bother with that for now. - public long contentUriGetFreeStorageSpace(String uriString) { + public long contentUriGetFreeStorageSpace(String fileName) { try { + Uri uri = Uri.parse(fileName); StorageManager storageManager = getApplicationContext().getSystemService(StorageManager.class); - // In 29 and later, we can directly get the UUID for the storage volume - // through the URI. - UUID volumeUUID; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - Uri uri = Uri.parse(uriString); - volumeUUID = UUID.fromString(storageManager.getStorageVolume(uri).getUuid()); - } else { - volumeUUID = storageManager.getUuidForPath(this.getFilesDir()); + ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(uri, "r"); + if (pfd == null) { + Log.w(TAG, "Failed to get free storage space from URI: " + fileName); + return -1; } - long availableBytes = storageManager.getAllocatableBytes(volumeUUID); - return availableBytes; - } catch (Exception e) { + StructStatVfs stats = Os.fstatvfs(pfd.getFileDescriptor()); + long freeSpace = stats.f_bavail * stats.f_bsize; + pfd.close(); + return freeSpace; + } catch (Exception e) { + // FileNotFoundException | ErrnoException e + // Log.getStackTraceString(e) Log.e(TAG, "contentUriGetFreeStorageSpace exception: " + e.toString()); return -1; } @@ -313,4 +392,14 @@ public class PpssppActivity extends NativeActivity { return -1; } } + + public boolean isExternalStoragePreservedLegacy() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // In 29 and later, we can check whether we got preserved storage legacy. + return Environment.isExternalStorageLegacy(); + } else { + // In 28 and earlier, we won't call this - we'll still request an exception. + return false; + } + } } diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index e70af303b1..52a23330ba 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -307,6 +307,38 @@ static int get_language_auto(void) } } +static std::string map_psp_language_to_i18n_locale(int val) +{ + switch (val) + { + default: + case PSP_SYSTEMPARAM_LANGUAGE_ENGLISH: + return "en_US"; + case PSP_SYSTEMPARAM_LANGUAGE_JAPANESE: + return "ja_JP"; + case PSP_SYSTEMPARAM_LANGUAGE_FRENCH: + return "fr_FR"; + case PSP_SYSTEMPARAM_LANGUAGE_GERMAN: + return "de_DE"; + case PSP_SYSTEMPARAM_LANGUAGE_SPANISH: + return "es_ES"; + case PSP_SYSTEMPARAM_LANGUAGE_ITALIAN: + return "it_IT"; + case PSP_SYSTEMPARAM_LANGUAGE_PORTUGUESE: + return "pt_PT"; + case PSP_SYSTEMPARAM_LANGUAGE_RUSSIAN: + return "ru_RU"; + case PSP_SYSTEMPARAM_LANGUAGE_DUTCH: + return "nl_NL"; + case PSP_SYSTEMPARAM_LANGUAGE_KOREAN: + return "ko_KR"; + case PSP_SYSTEMPARAM_LANGUAGE_CHINESE_TRADITIONAL: + return "zh_TW"; + case PSP_SYSTEMPARAM_LANGUAGE_CHINESE_SIMPLIFIED: + return "zh_CN"; + } +} + static void check_variables(CoreParameter &coreParam) { bool updated = false; @@ -348,15 +380,7 @@ static void check_variables(CoreParameter &coreParam) if (g_Config.iLanguage < 0) g_Config.iLanguage = get_language_auto(); - g_Config.sLanguageIni = "en_US"; - auto langValuesMapping = GetLangValuesMapping(); - for (auto i = langValuesMapping.begin(); i != langValuesMapping.end(); ++i) - { - if (i->second.second == g_Config.iLanguage) - { - g_Config.sLanguageIni = i->first; - } - } + g_Config.sLanguageIni = map_psp_language_to_i18n_locale(g_Config.iLanguage); i18nrepo.LoadIni(g_Config.sLanguageIni); if (!PSP_IsInited() && ppsspp_internal_resolution.Update(&g_Config.iInternalResolution))