From 585b6e9d46b207a6b48a021ea35636fb8c92b405 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
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 22c9b05de..5fe235dba 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 eafcf9e42..738275297 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 2eb5c4349..b1db87e41 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 <android/native_window_jni.h>
-#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 298a79bac..1dd826a4a 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<jstring>(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<const char16_t*>(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 b441c2a12..2c9234313 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 0c4c88cde..c3a81f9a9 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 4c7aba3f5..72c481798 100644
--- a/src/common/string_util.cpp
+++ b/src/common/string_util.cpp
@@ -14,6 +14,10 @@
 #include <windows.h>
 #endif
 
+#ifdef ANDROID
+#include <common/fs/fs_android.h>
+#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