From 585b6e9d46b207a6b48a021ea35636fb8c92b405 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 24 Oct 2023 22:51:09 -0400 Subject: [PATCH] android: Fix resolving android URIs in native code --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 29 +++++++++++++--- .../org/yuzu/yuzu_emu/utils/DocumentsTree.kt | 17 ++++++++++ src/android/app/src/main/jni/native.h | 12 +++---- src/common/fs/fs_android.cpp | 33 +++++++++++++++++++ src/common/fs/fs_android.h | 15 +++++++++ src/common/fs/path_util.cpp | 10 ++++++ src/common/string_util.cpp | 12 +++++++ 7 files changed, 117 insertions(+), 11 deletions(-) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 22c9b05de9..5fe235dba3 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -5,6 +5,7 @@ package org.yuzu.yuzu_emu import android.app.Dialog import android.content.DialogInterface +import android.net.Uri import android.os.Bundle import android.text.Html import android.text.method.LinkMovementMethod @@ -16,7 +17,7 @@ import androidx.fragment.app.DialogFragment import com.google.android.material.dialog.MaterialAlertDialogBuilder import java.lang.ref.WeakReference import org.yuzu.yuzu_emu.activities.EmulationActivity -import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath +import org.yuzu.yuzu_emu.utils.DocumentsTree import org.yuzu.yuzu_emu.utils.FileUtil import org.yuzu.yuzu_emu.utils.Log import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable @@ -68,7 +69,7 @@ object NativeLibrary { @Keep @JvmStatic fun openContentUri(path: String?, openmode: String?): Int { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.openContentUri(path, openmode) } else { FileUtil.openContentUri(path, openmode) @@ -78,7 +79,7 @@ object NativeLibrary { @Keep @JvmStatic fun getSize(path: String?): Long { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.getFileSize(path) } else { FileUtil.getFileSize(path) @@ -88,7 +89,7 @@ object NativeLibrary { @Keep @JvmStatic fun exists(path: String?): Boolean { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.exists(path) } else { FileUtil.exists(path) @@ -98,13 +99,31 @@ object NativeLibrary { @Keep @JvmStatic fun isDirectory(path: String?): Boolean { - return if (isNativePath(path!!)) { + return if (DocumentsTree.isNativePath(path!!)) { YuzuApplication.documentsTree!!.isDirectory(path) } else { FileUtil.isDirectory(path) } } + @Keep + @JvmStatic + fun getParentDirectory(path: String): String = + if (DocumentsTree.isNativePath(path)) { + YuzuApplication.documentsTree!!.getParentDirectory(path) + } else { + path + } + + @Keep + @JvmStatic + fun getFilename(path: String): String = + if (DocumentsTree.isNativePath(path)) { + YuzuApplication.documentsTree!!.getFilename(path) + } else { + FileUtil.getFilename(Uri.parse(path)) + } + /** * Returns true if pro controller isn't available and handheld is */ diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt index eafcf9e427..7382752970 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt @@ -42,6 +42,23 @@ class DocumentsTree { return node != null && node.isDirectory } + fun getParentDirectory(filepath: String): String { + val node = resolvePath(filepath)!! + val parentNode = node.parent + if (parentNode != null && parentNode.isDirectory) { + return parentNode.uri!!.toString() + } + return node.uri!!.toString() + } + + fun getFilename(filepath: String): String { + val node = resolvePath(filepath) + if (node != null) { + return node.name!! + } + return filepath + } + private fun resolvePath(filepath: String): DocumentsNode? { val tokens = StringTokenizer(filepath, File.separator, false) var iterator = root diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 2eb5c43496..b1db87e41a 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -2,14 +2,14 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include -#include "core/core.h" -#include "core/perf_stats.h" -#include "jni/emu_window/emu_window.h" -#include "jni/applets/software_keyboard.h" -#include "video_core/rasterizer_interface.h" #include "common/detached_tasks.h" -#include "core/hle/service/acc/profile_manager.h" +#include "core/core.h" #include "core/file_sys/registered_cache.h" +#include "core/hle/service/acc/profile_manager.h" +#include "core/perf_stats.h" +#include "jni/applets/software_keyboard.h" +#include "jni/emu_window/emu_window.h" +#include "video_core/rasterizer_interface.h" #pragma once diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp index 298a79bac2..1dd826a4a4 100644 --- a/src/common/fs/fs_android.cpp +++ b/src/common/fs/fs_android.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/fs/fs_android.h" +#include "common/string_util.h" namespace Common::FS::Android { @@ -28,28 +29,35 @@ void RegisterCallbacks(JNIEnv* env, jclass clazz) { env->GetJavaVM(&g_jvm); native_library = clazz; +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \ + F(JMethodID, JMethodName, Signature) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ F(JMethodID, JMethodName, Signature) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ F(JMethodID, JMethodName, Signature) #define F(JMethodID, JMethodName, Signature) \ JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature); + ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_STORAGE_FUNCTIONS(FS) #undef F #undef FS #undef FR +#undef FH } void UnRegisterCallbacks() { +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) #define F(JMethodID) JMethodID = nullptr; + ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_STORAGE_FUNCTIONS(FS) #undef F #undef FS #undef FR +#undef FH } bool IsContentUri(const std::string& path) { @@ -95,4 +103,29 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) #undef F #undef FR +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \ + F(FunctionName, JMethodID, Caller) +#define F(FunctionName, JMethodID, Caller) \ + std::string FunctionName(const std::string& filepath) { \ + if (JMethodID == nullptr) { \ + return 0; \ + } \ + auto env = GetEnvForThread(); \ + jstring j_filepath = env->NewStringUTF(filepath.c_str()); \ + jstring j_return = \ + static_cast(env->Caller(native_library, JMethodID, j_filepath)); \ + if (!j_return) { \ + return {}; \ + } \ + const jchar* jchars = env->GetStringChars(j_return, nullptr); \ + const jsize length = env->GetStringLength(j_return); \ + const std::u16string_view string_view(reinterpret_cast(jchars), length); \ + const std::string converted_string = Common::UTF16ToUTF8(string_view); \ + env->ReleaseStringChars(j_return, jchars); \ + return converted_string; \ + } +ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) +#undef F +#undef FH + } // namespace Common::FS::Android diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h index b441c2a128..2c92343133 100644 --- a/src/common/fs/fs_android.h +++ b/src/common/fs/fs_android.h @@ -17,19 +17,28 @@ "(Ljava/lang/String;)Z") \ V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") +#define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \ + V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \ + "(Ljava/lang/String;)Ljava/lang/String;") \ + V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \ + "(Ljava/lang/String;)Ljava/lang/String;") + namespace Common::FS::Android { static JavaVM* g_jvm = nullptr; static jclass native_library = nullptr; +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) #define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) #define F(JMethodID) static jmethodID JMethodID = nullptr; +ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) ANDROID_STORAGE_FUNCTIONS(FS) #undef F #undef FS #undef FR +#undef FH enum class OpenMode { Read, @@ -62,4 +71,10 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) #undef F #undef FR +#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName) +#define F(FunctionName) std::string FunctionName(const std::string& filepath); +ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH) +#undef F +#undef FH + } // namespace Common::FS::Android diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index 0c4c88cded..c3a81f9a9d 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -401,6 +401,16 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se } std::string_view GetParentPath(std::string_view path) { + if (path.empty()) { + return path; + } + +#ifdef ANDROID + if (path[0] != '/') { + std::string path_string{path}; + return FS::Android::GetParentDirectory(path_string); + } +#endif const auto name_bck_index = path.rfind('\\'); const auto name_fwd_index = path.rfind('/'); std::size_t name_index; diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index 4c7aba3f5c..72c4817989 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -14,6 +14,10 @@ #include #endif +#ifdef ANDROID +#include +#endif + namespace Common { /// Make a string lowercase @@ -63,6 +67,14 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _ if (full_path.empty()) return false; +#ifdef ANDROID + if (full_path[0] != '/') { + *_pPath = Common::FS::Android::GetParentDirectory(full_path); + *_pFilename = Common::FS::Android::GetFilename(full_path); + return true; + } +#endif + std::size_t dir_end = full_path.find_last_of("/" // windows needs the : included for something like just "C:" to be considered a directory #ifdef _WIN32