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;
}