diff --git a/CMakeLists.txt b/CMakeLists.txt index b46ef5c72..053707f31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -614,6 +614,8 @@ add_library(Common STATIC Common/File/VFS/DirectoryReader.h Common/File/AndroidStorage.h Common/File/AndroidStorage.cpp + Common/File/AndroidContentURI.h + Common/File/AndroidContentURI.cpp Common/File/DiskFree.h Common/File/DiskFree.cpp Common/File/Path.h diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj index 5492587f7..67e208b17 100644 --- a/Common/Common.vcxproj +++ b/Common/Common.vcxproj @@ -434,6 +434,7 @@ + @@ -874,6 +875,7 @@ + diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters index 879e3495b..b24536cc1 100644 --- a/Common/Common.vcxproj.filters +++ b/Common/Common.vcxproj.filters @@ -497,6 +497,9 @@ System + + File + @@ -926,6 +929,9 @@ System + + File + diff --git a/Common/File/AndroidContentURI.cpp b/Common/File/AndroidContentURI.cpp new file mode 100644 index 000000000..65526285e --- /dev/null +++ b/Common/File/AndroidContentURI.cpp @@ -0,0 +1,182 @@ +#include "Common/File/AndroidContentURI.h" + +bool AndroidContentURI::Parse(const std::string &path) { + const char *prefix = "content://"; + if (!startsWith(path, prefix)) { + return false; + } + + std::string components = path.substr(strlen(prefix)); + + std::vector parts; + SplitString(components, '/', parts); + if (parts.size() == 3) { + // Single file URI. + provider = parts[0]; + if (parts[1] == "tree") { + // Single directory URI. + // Not sure when we encounter these? + // file empty signals this type. + root = UriDecode(parts[2]); + return true; + } else if (parts[1] == "document") { + // root empty signals this type. + file = UriDecode(parts[2]); + return true; + } else { + // What's this? + return false; + } + } else if (parts.size() == 5) { + // Tree URI + provider = parts[0]; + if (parts[1] != "tree") { + return false; + } + root = UriDecode(parts[2]); + if (parts[3] != "document") { + return false; + } + file = UriDecode(parts[4]); + // Sanity check file path. + return startsWith(file, root); + } else { + // Invalid Content URI + return false; + } +} + +AndroidContentURI AndroidContentURI::WithRootFilePath(const std::string &filePath) { + if (root.empty()) { + ERROR_LOG(SYSTEM, "WithRootFilePath cannot be used with single file URIs."); + return *this; + } + + AndroidContentURI uri = *this; + uri.file = uri.root; + if (!filePath.empty()) { + uri.file += "/" + filePath; + } + return uri; +} + +AndroidContentURI AndroidContentURI::WithComponent(const std::string &filePath) { + AndroidContentURI uri = *this; + uri.file = uri.file + "/" + filePath; + return uri; +} + +AndroidContentURI AndroidContentURI::WithExtraExtension(const std::string &extension) { + AndroidContentURI uri = *this; + uri.file = uri.file + extension; + return uri; +} + +AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const { + _dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.'); + _dbg_assert_(!newExtension.empty() && newExtension[0] == '.'); + AndroidContentURI uri = *this; + if (endsWithNoCase(file, oldExtension)) { + uri.file = file.substr(0, file.size() - oldExtension.size()) + newExtension; + } + return uri; +} + +AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &newExtension) const { + _dbg_assert_(!newExtension.empty() && newExtension[0] == '.'); + AndroidContentURI uri = *this; + if (file.empty()) { + return uri; + } + std::string extension = GetFileExtension(); + uri.file = file.substr(0, file.size() - extension.size()) + newExtension; + return uri; +} + +bool AndroidContentURI::CanNavigateUp() const { + if (root.empty()) { + return false; + } + return file.size() > root.size(); +} + +// Only goes downwards in hierarchies. No ".." will ever be generated. +bool AndroidContentURI::ComputePathTo(const AndroidContentURI &other, std::string &path) const { + size_t offset = FilePath().size() + 1; + std::string otherFilePath = other.FilePath(); + if (offset >= otherFilePath.size()) { + ERROR_LOG(SYSTEM, "Bad call to PathTo. '%s' -> '%s'", FilePath().c_str(), other.FilePath().c_str()); + return false; + } + + path = other.FilePath().substr(FilePath().size() + 1); + return true; +} + +std::string AndroidContentURI::GetFileExtension() const { + size_t pos = file.rfind("."); + if (pos == std::string::npos) { + return ""; + } + size_t slash_pos = file.rfind("/"); + if (slash_pos != std::string::npos && slash_pos > pos) { + // Don't want to detect "df/file" from "/as.df/file" + return ""; + } + std::string ext = file.substr(pos); + for (size_t i = 0; i < ext.size(); i++) { + ext[i] = tolower(ext[i]); + } + return ext; +} + +std::string AndroidContentURI::GetLastPart() const { + if (file.empty()) { + // Can't do anything anyway. + return std::string(); + } + + if (!CanNavigateUp()) { + size_t colon = file.rfind(':'); + if (colon == std::string::npos) { + return std::string(); + } + return file.substr(colon + 1); + } + + size_t slash = file.rfind('/'); + if (slash == std::string::npos) { + return std::string(); + } + + std::string part = file.substr(slash + 1); + return part; +} + +bool AndroidContentURI::NavigateUp() { + if (!CanNavigateUp()) { + return false; + } + + size_t slash = file.rfind('/'); + if (slash == std::string::npos) { + return false; + } + + file = file.substr(0, slash); + return true; +} + + +std::string AndroidContentURI::ToString() const { + if (file.empty()) { + // Tree URI + return StringFromFormat("content://%s/tree/%s", provider.c_str(), UriEncode(root).c_str()); + } else if (root.empty()) { + // Single file URI + return StringFromFormat("content://%s/document/%s", provider.c_str(), UriEncode(file).c_str()); + } else { + // File URI from Tree + return StringFromFormat("content://%s/tree/%s/document/%s", provider.c_str(), UriEncode(root).c_str(), UriEncode(file).c_str()); + } +} diff --git a/Common/File/AndroidContentURI.h b/Common/File/AndroidContentURI.h new file mode 100644 index 000000000..3020b4147 --- /dev/null +++ b/Common/File/AndroidContentURI.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include "Common/StringUtils.h" +#include "Common/Net/URL.h" +#include "Common/Log.h" + +// Utility to deal with Android storage URIs of the forms: +// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO +// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO + +// This file compiles on all platforms, to reduce the need for ifdefs. + +// I am not 100% sure it's OK to rely on the internal format of file content URIs. +// On the other hand, I'm sure tons of apps would break if these changed, so I think we can +// consider them pretty stable. Additionally, the official Document library just manipulates the URIs +// in similar ways... +class AndroidContentURI { +private: + std::string provider; + std::string root; + std::string file; +public: + AndroidContentURI() {} + explicit AndroidContentURI(const std::string &path) { + Parse(path); + } + + bool Parse(const std::string &path); + + AndroidContentURI WithRootFilePath(const std::string &filePath); + AndroidContentURI WithComponent(const std::string &filePath); + AndroidContentURI WithExtraExtension(const std::string &extension); + AndroidContentURI WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const; + AndroidContentURI WithReplacedExtension(const std::string &newExtension) const; + + bool CanNavigateUp() const; + + // Only goes downwards in hierarchies. No ".." will ever be generated. + bool ComputePathTo(const AndroidContentURI &other, std::string &path) const; + + std::string GetFileExtension() const; + std::string GetLastPart() const; + + bool NavigateUp(); + + bool TreeContains(const AndroidContentURI &fileURI) { + if (root.empty()) { + return false; + } + return startsWith(fileURI.file, root); + } + + std::string ToString() const; + + // Never store the output of this, only show it to the user. + std::string ToVisualString() const { + return file; + } + + const std::string &FilePath() const { + return file; + } + + const std::string &RootPath() const { + return root.empty() ? file : root; + } +}; diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index e8088e90b..41a338f77 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -25,7 +25,6 @@ #include "ppsspp_config.h" #include "android/jni/app-android.h" -#include "android/jni/AndroidContentURI.h" #ifdef __MINGW32__ #include @@ -33,12 +32,14 @@ #define _POSIX_THREAD_SAFE_FUNCTIONS 200112L #endif #endif + #include #include #include #include "Common/Log.h" #include "Common/LogReporting.h" +#include "Common/File/AndroidContentURI.h" #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" #include "Common/SysError.h" diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index 815593016..b80a5e7ca 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -4,13 +4,13 @@ #include #include "Common/File/Path.h" +#include "Common/File/AndroidContentURI.h" #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" #include "Common/Log.h" #include "Common/Data/Encoding/Utf8.h" #include "android/jni/app-android.h" -#include "android/jni/AndroidContentURI.h" #if HOST_IS_CASE_SENSITIVE #include @@ -157,7 +157,7 @@ std::string Path::GetFilename() const { return path_; } -static std::string GetExtFromString(const std::string &str) { +std::string GetExtFromString(const std::string &str) { size_t pos = str.rfind("."); if (pos == std::string::npos) { return ""; @@ -177,7 +177,7 @@ static std::string GetExtFromString(const std::string &str) { std::string Path::GetFileExtension() const { if (type_ == PathType::CONTENT_URI) { AndroidContentURI uri(path_); - return GetExtFromString(uri.FilePath()); + return uri.GetFileExtension(); } return GetExtFromString(path_); } diff --git a/Common/File/Path.h b/Common/File/Path.h index ac93f9a9b..3e09c0c3c 100644 --- a/Common/File/Path.h +++ b/Common/File/Path.h @@ -133,6 +133,8 @@ private: PathType type_; }; +// Utility function for parsing out file extensions. +std::string GetExtFromString(const std::string &str); // Utility function for fixing the case of paths. Only present on Unix-like systems. diff --git a/Common/File/PathBrowser.cpp b/Common/File/PathBrowser.cpp index f5ab1a33b..36e03dca5 100644 --- a/Common/File/PathBrowser.cpp +++ b/Common/File/PathBrowser.cpp @@ -17,7 +17,6 @@ #if PPSSPP_PLATFORM(ANDROID) #include "android/jni/app-android.h" -#include "android/jni/AndroidContentURI.h" #endif bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector &files) { diff --git a/Common/GPU/Vulkan/VulkanQueueRunner.cpp b/Common/GPU/Vulkan/VulkanQueueRunner.cpp index 27e96e39a..3e2c26934 100644 --- a/Common/GPU/Vulkan/VulkanQueueRunner.cpp +++ b/Common/GPU/Vulkan/VulkanQueueRunner.cpp @@ -9,7 +9,7 @@ using namespace PPSSPP_VK; -// Debug help: adb logcat -s DEBUG PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState +// Debug help: adb logcat -s DEBUG AndroidRuntime PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState PpssppActivity CameraHelper static void MergeRenderAreaRectInto(VkRect2D *dest, const VkRect2D &src) { if (dest->offset.x > src.offset.x) { diff --git a/Core/ELF/ParamSFO.cpp b/Core/ELF/ParamSFO.cpp index 6d05d8814..d6717af0b 100644 --- a/Core/ELF/ParamSFO.cpp +++ b/Core/ELF/ParamSFO.cpp @@ -93,6 +93,20 @@ std::vector ParamSFOData::GetKeys() const { return result; } +std::string ParamSFOData::GetDiscID() { + const std::string discID = GetValueString("DISC_ID"); + if (discID.empty()) { + std::string fakeID = GenerateFakeID(); + WARN_LOG(LOADER, "No DiscID found - generating a fake one: '%s' (from %s)", fakeID.c_str(), PSP_CoreParameter().fileToStart.c_str()); + ValueData data; + data.type = VT_UTF8; + data.s_value = fakeID; + values["DISC_ID"] = data; + return fakeID; + } + return discID; +} + // I'm so sorry Ced but this is highly endian unsafe :( bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) { if (size < sizeof(Header)) @@ -319,7 +333,12 @@ std::string ParamSFOData::GenerateFakeID(std::string filename) const { int sumOfAllLetters = 0; for (char &c : file) { sumOfAllLetters += c; - c = toupper(c); + // Get rid of some garbage characters than can arise when opening content URIs. Well, I've only seen '%', but... + if (strchr("%() []", c) != nullptr) { + c = 'X'; + } else { + c = toupper(c); + } } if (file.size() < 4) { diff --git a/Core/ELF/ParamSFO.h b/Core/ELF/ParamSFO.h index e0bb48ec9..0e5afab4b 100644 --- a/Core/ELF/ParamSFO.h +++ b/Core/ELF/ParamSFO.h @@ -38,19 +38,7 @@ public: std::vector GetKeys() const; std::string GenerateFakeID(std::string filename = "") const; - std::string GetDiscID() { - const std::string discID = GetValueString("DISC_ID"); - if (discID.empty()) { - std::string fakeID = GenerateFakeID(); - WARN_LOG(LOADER, "No DiscID found - generating a fake one: '%s'", fakeID.c_str()); - ValueData data; - data.type = VT_UTF8; - data.s_value = fakeID; - values["DISC_ID"] = data; - return fakeID; - } - return discID; - } + std::string GetDiscID(); bool ReadSFO(const u8 *paramsfo, size_t size); bool WriteSFO(u8 **paramsfo, size_t *size) const; diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 5657bd281..344f672b0 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -128,6 +128,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin // OK, quick methods of identification for common types failed. Moving on to more expensive methods, // starting by reading the first few bytes. + // This can be necessary for weird Android content storage path types, see issue #17462 u32_le id; @@ -170,9 +171,9 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin if (id == 'FLE\x7F') { Path filename = fileLoader->GetPath(); - // There are a few elfs misnamed as pbp (like Trig Wars), accept that. + // There are a few elfs misnamed as pbp (like Trig Wars), accept that. Also accept extension-less paths. if (extension == ".plf" || strstr(filename.GetFilename().c_str(), "BOOT.BIN") || - extension == ".elf" || extension == ".prx" || extension == ".pbp") { + extension == ".elf" || extension == ".prx" || extension == ".pbp" || extension == "") { return IdentifiedFileType::PSP_ELF; } return IdentifiedFileType::UNKNOWN_ELF; @@ -296,11 +297,12 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { return false; } } + // Looks like a wrong fall through but is not, both paths are handled above. case IdentifiedFileType::PSP_PBP: case IdentifiedFileType::PSP_ELF: { - INFO_LOG(LOADER, "File is an ELF or loose PBP!"); + INFO_LOG(LOADER, "File is an ELF or loose PBP! %s", fileLoader->GetPath().c_str()); return Load_PSP_ELF_PBP(fileLoader, error_string); } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index a427f12dc..b77512dc7 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -55,6 +55,7 @@ #include "UI/Theme.h" #include "Common/File/FileUtil.h" +#include "Common/File/AndroidContentURI.h" #include "Common/OSVersion.h" #include "Common/TimeUtil.h" #include "Common/StringUtils.h" @@ -88,7 +89,6 @@ #if PPSSPP_PLATFORM(ANDROID) #include "android/jni/AndroidAudio.h" -#include "android/jni/AndroidContentURI.h" extern AndroidAudioState *g_audioState; diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj index 8480eb490..353aa305b 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj +++ b/UWP/CommonUWP/CommonUWP.vcxproj @@ -285,6 +285,7 @@ + @@ -440,6 +441,7 @@ + diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters index 67e141a43..18faba3ae 100644 --- a/UWP/CommonUWP/CommonUWP.vcxproj.filters +++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters @@ -432,6 +432,9 @@ System + + File + @@ -817,6 +820,9 @@ System + + File + diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 64005a3f4..07aa70657 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -177,6 +177,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Common/Data/Text/Parsers.cpp \ $(SRC)/Common/Data/Text/WrapText.cpp \ $(SRC)/Common/File/AndroidStorage.cpp \ + $(SRC)/Common/File/AndroidContentURI.cpp \ $(SRC)/Common/File/VFS/VFS.cpp \ $(SRC)/Common/File/VFS/ZipFileReader.cpp \ $(SRC)/Common/File/VFS/DirectoryReader.cpp \ diff --git a/android/jni/AndroidContentURI.h b/android/jni/AndroidContentURI.h deleted file mode 100644 index 07977a2d7..000000000 --- a/android/jni/AndroidContentURI.h +++ /dev/null @@ -1,229 +0,0 @@ -#pragma once - -#include - -#include "Common/StringUtils.h" -#include "Common/Net/URL.h" -#include "Common/Log.h" - -// Utility to deal with Android storage URIs of the forms: -// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO -// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO - -// This file compiles on all platforms, to reduce the need for ifdefs. - -// I am not 100% sure it's OK to rely on the internal format of file content URIs. -// On the other hand, I'm sure tons of apps would break if these changed, so I think we can -// consider them pretty stable. Additionally, the official Document library just manipulates the URIs -// in similar ways... -class AndroidContentURI { -private: - std::string provider; - std::string root; - std::string file; -public: - AndroidContentURI() {} - explicit AndroidContentURI(const std::string &path) { - Parse(path); - } - - bool Parse(const std::string &path) { - const char *prefix = "content://"; - if (!startsWith(path, prefix)) { - return false; - } - - std::string components = path.substr(strlen(prefix)); - - std::vector parts; - SplitString(components, '/', parts); - if (parts.size() == 3) { - // Single file URI. - provider = parts[0]; - if (parts[1] == "tree") { - // Single directory URI. - // Not sure when we encounter these? - // file empty signals this type. - root = UriDecode(parts[2]); - return true; - } else if (parts[1] == "document") { - // root empty signals this type. - file = UriDecode(parts[2]); - return true; - } else { - // What's this? - return false; - } - } else if (parts.size() == 5) { - // Tree URI - provider = parts[0]; - if (parts[1] != "tree") { - return false; - } - root = UriDecode(parts[2]); - if (parts[3] != "document") { - return false; - } - file = UriDecode(parts[4]); - // Sanity check file path. - return startsWith(file, root); - } else { - // Invalid Content URI - return false; - } - } - - AndroidContentURI WithRootFilePath(const std::string &filePath) { - if (root.empty()) { - ERROR_LOG(SYSTEM, "WithRootFilePath cannot be used with single file URIs."); - return *this; - } - - AndroidContentURI uri = *this; - uri.file = uri.root; - if (!filePath.empty()) { - uri.file += "/" + filePath; - } - return uri; - } - - AndroidContentURI WithComponent(const std::string &filePath) { - AndroidContentURI uri = *this; - uri.file = uri.file + "/" + filePath; - return uri; - } - - AndroidContentURI WithExtraExtension(const std::string &extension) { - AndroidContentURI uri = *this; - uri.file = uri.file + extension; - return uri; - } - - AndroidContentURI WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const { - _dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.'); - _dbg_assert_(!newExtension.empty() && newExtension[0] == '.'); - AndroidContentURI uri = *this; - if (endsWithNoCase(file, oldExtension)) { - uri.file = file.substr(0, file.size() - oldExtension.size()) + newExtension; - } - return uri; - } - - AndroidContentURI WithReplacedExtension(const std::string &newExtension) const { - _dbg_assert_(!newExtension.empty() && newExtension[0] == '.'); - AndroidContentURI uri = *this; - if (file.empty()) { - return uri; - } - std::string extension = GetFileExtension(); - uri.file = file.substr(0, file.size() - extension.size()) + newExtension; - return uri; - } - - bool CanNavigateUp() const { - if (root.empty()) { - return false; - } - return file.size() > root.size(); - } - - // Only goes downwards in hierarchies. No ".." will ever be generated. - bool ComputePathTo(const AndroidContentURI &other, std::string &path) const { - size_t offset = FilePath().size() + 1; - std::string otherFilePath = other.FilePath(); - if (offset >= otherFilePath.size()) { - ERROR_LOG(SYSTEM, "Bad call to PathTo. '%s' -> '%s'", FilePath().c_str(), other.FilePath().c_str()); - return false; - } - - path = other.FilePath().substr(FilePath().size() + 1); - return true; - } - - std::string GetFileExtension() const { - size_t pos = file.rfind("."); - if (pos == std::string::npos) { - return ""; - } - size_t slash_pos = file.rfind("/"); - if (slash_pos != std::string::npos && slash_pos > pos) { - // Don't want to detect "df/file" from "/as.df/file" - return ""; - } - std::string ext = file.substr(pos); - for (size_t i = 0; i < ext.size(); i++) { - ext[i] = tolower(ext[i]); - } - return ext; - } - - std::string GetLastPart() const { - if (file.empty()) { - // Can't do anything anyway. - return std::string(); - } - - if (!CanNavigateUp()) { - size_t colon = file.rfind(':'); - if (colon == std::string::npos) { - return std::string(); - } - return file.substr(colon + 1); - } - - size_t slash = file.rfind('/'); - if (slash == std::string::npos) { - return std::string(); - } - - std::string part = file.substr(slash + 1); - return part; - } - - bool NavigateUp() { - if (!CanNavigateUp()) { - return false; - } - - size_t slash = file.rfind('/'); - if (slash == std::string::npos) { - return false; - } - - file = file.substr(0, slash); - return true; - } - - bool TreeContains(const AndroidContentURI &fileURI) { - if (root.empty()) { - return false; - } - return startsWith(fileURI.file, root); - } - - std::string ToString() const { - if (file.empty()) { - // Tree URI - return StringFromFormat("content://%s/tree/%s", provider.c_str(), UriEncode(root).c_str()); - } else if (root.empty()) { - // Single file URI - return StringFromFormat("content://%s/document/%s", provider.c_str(), UriEncode(file).c_str()); - } else { - // File URI from Tree - return StringFromFormat("content://%s/tree/%s/document/%s", provider.c_str(), UriEncode(root).c_str(), UriEncode(file).c_str()); - } - } - - // Never store the output of this, only show it to the user. - std::string ToVisualString() const { - return file; - } - - const std::string &FilePath() const { - return file; - } - - const std::string &RootPath() const { - return root.empty() ? file : root; - } -}; diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 609c41146..740eef13b 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1196,7 +1196,9 @@ public abstract class NativeActivity extends Activity { String path = selectedDirectoryUri.toString(); Log.i(TAG, "Browse folder finished: " + path); try { - getContentResolver().takePersistableUriPermission(selectedDirectoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + if (Build.VERSION.SDK_INT >= 19) { + getContentResolver().takePersistableUriPermission(selectedDirectoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } } catch (Exception e) { Log.w(TAG, "Exception getting permissions for document: " + e.toString()); } diff --git a/libretro/Makefile.common b/libretro/Makefile.common index e5d39bc96..05754192e 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -276,6 +276,7 @@ SOURCES_CXX += \ $(COMMONDIR)/File/VFS/DirectoryReader.cpp \ $(COMMONDIR)/File/VFS/ZipFileReader.cpp \ $(COMMONDIR)/File/AndroidStorage.cpp \ + $(COMMONDIR)/File/AndroidContentURI.cpp \ $(COMMONDIR)/File/DiskFree.cpp \ $(COMMONDIR)/File/Path.cpp \ $(COMMONDIR)/File/PathBrowser.cpp \ diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 0c41d6302..2aa53e852 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -73,7 +73,7 @@ #include "GPU/Common/TextureDecoder.h" #include "GPU/Common/GPUStateUtils.h" -#include "android/jni/AndroidContentURI.h" +#include "Common/File/AndroidContentURI.h" #include "unittest/JitHarness.h" #include "unittest/TestVertexJit.h" @@ -692,7 +692,7 @@ static bool TestAndroidContentURI() { static const char *directoryURIString = "content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO"; static const char *fileTreeURIString = "content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO%2FTekken%206.iso"; static const char *fileNonTreeString = "content://com.android.externalstorage.documents/document/primary%3APSP%2Fcrash_bad_execaddr.prx"; - + static const char *downloadURIString = "content://com.android.providers.downloads.documents/document/msf%3A10000000006"; AndroidContentURI treeURI; EXPECT_TRUE(treeURI.Parse(std::string(treeURIString))); @@ -724,6 +724,12 @@ static bool TestAndroidContentURI() { EXPECT_EQ_STR(fileURI.GetFileExtension(), std::string(".prx")); EXPECT_FALSE(fileURI.CanNavigateUp()); + // These are annoying because they hide the actual filename, and we can't get at a parent folder, + // which confuses our elf loading. + AndroidContentURI downloadURI; + EXPECT_TRUE(downloadURI.Parse(std::string(downloadURIString))); + EXPECT_EQ_STR(downloadURI.GetLastPart(), std::string("10000000006")); + EXPECT_FALSE(downloadURI.CanNavigateUp()); return true; }