summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar liamwhite2023-10-30 13:28:11 -0400
committerGravatar GitHub2023-10-30 13:28:11 -0400
commit07276cf62ad5a32ae7a9867029a05a4fbb141d5c (patch)
tree3eaac7c7fe07db8b44893341ec885e4dfc31d8c8
parentMerge pull request #11920 from Termynat0r/master (diff)
parentandroid: FileUtil: Add option to suppress log for native exists() calls (diff)
downloadyuzu-07276cf62ad5a32ae7a9867029a05a4fbb141d5c.tar.gz
yuzu-07276cf62ad5a32ae7a9867029a05a4fbb141d5c.tar.xz
yuzu-07276cf62ad5a32ae7a9867029a05a4fbb141d5c.zip
Merge pull request #11908 from t895/log-spam
android: Fix URI parsing in native code
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt2
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt9
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt6
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt20
-rw-r--r--src/android/app/src/main/jni/CMakeLists.txt2
-rw-r--r--src/android/app/src/main/jni/game_metadata.cpp112
-rw-r--r--src/android/app/src/main/jni/native.cpp757
-rw-r--r--src/android/app/src/main/jni/native.h84
-rw-r--r--src/common/fs/fs_android.cpp33
-rw-r--r--src/common/fs/fs_android.h15
-rw-r--r--src/common/fs/path_util.cpp10
-rw-r--r--src/common/string_util.cpp12
17 files changed, 662 insertions, 516 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 115f72710..e2c5b6acd 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
5 5
6import android.app.Dialog 6import android.app.Dialog
7import android.content.DialogInterface 7import android.content.DialogInterface
8import android.net.Uri
8import android.os.Bundle 9import android.os.Bundle
9import android.text.Html 10import android.text.Html
10import android.text.method.LinkMovementMethod 11import android.text.method.LinkMovementMethod
@@ -16,7 +17,7 @@ import androidx.fragment.app.DialogFragment
16import com.google.android.material.dialog.MaterialAlertDialogBuilder 17import com.google.android.material.dialog.MaterialAlertDialogBuilder
17import java.lang.ref.WeakReference 18import java.lang.ref.WeakReference
18import org.yuzu.yuzu_emu.activities.EmulationActivity 19import org.yuzu.yuzu_emu.activities.EmulationActivity
19import org.yuzu.yuzu_emu.utils.DocumentsTree.Companion.isNativePath 20import org.yuzu.yuzu_emu.utils.DocumentsTree
20import org.yuzu.yuzu_emu.utils.FileUtil 21import org.yuzu.yuzu_emu.utils.FileUtil
21import org.yuzu.yuzu_emu.utils.Log 22import org.yuzu.yuzu_emu.utils.Log
22import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable 23import org.yuzu.yuzu_emu.utils.SerializableHelper.serializable
@@ -68,7 +69,7 @@ object NativeLibrary {
68 @Keep 69 @Keep
69 @JvmStatic 70 @JvmStatic
70 fun openContentUri(path: String?, openmode: String?): Int { 71 fun openContentUri(path: String?, openmode: String?): Int {
71 return if (isNativePath(path!!)) { 72 return if (DocumentsTree.isNativePath(path!!)) {
72 YuzuApplication.documentsTree!!.openContentUri(path, openmode) 73 YuzuApplication.documentsTree!!.openContentUri(path, openmode)
73 } else { 74 } else {
74 FileUtil.openContentUri(path, openmode) 75 FileUtil.openContentUri(path, openmode)
@@ -78,7 +79,7 @@ object NativeLibrary {
78 @Keep 79 @Keep
79 @JvmStatic 80 @JvmStatic
80 fun getSize(path: String?): Long { 81 fun getSize(path: String?): Long {
81 return if (isNativePath(path!!)) { 82 return if (DocumentsTree.isNativePath(path!!)) {
82 YuzuApplication.documentsTree!!.getFileSize(path) 83 YuzuApplication.documentsTree!!.getFileSize(path)
83 } else { 84 } else {
84 FileUtil.getFileSize(path) 85 FileUtil.getFileSize(path)
@@ -88,23 +89,41 @@ object NativeLibrary {
88 @Keep 89 @Keep
89 @JvmStatic 90 @JvmStatic
90 fun exists(path: String?): Boolean { 91 fun exists(path: String?): Boolean {
91 return if (isNativePath(path!!)) { 92 return if (DocumentsTree.isNativePath(path!!)) {
92 YuzuApplication.documentsTree!!.exists(path) 93 YuzuApplication.documentsTree!!.exists(path)
93 } else { 94 } else {
94 FileUtil.exists(path) 95 FileUtil.exists(path, suppressLog = true)
95 } 96 }
96 } 97 }
97 98
98 @Keep 99 @Keep
99 @JvmStatic 100 @JvmStatic
100 fun isDirectory(path: String?): Boolean { 101 fun isDirectory(path: String?): Boolean {
101 return if (isNativePath(path!!)) { 102 return if (DocumentsTree.isNativePath(path!!)) {
102 YuzuApplication.documentsTree!!.isDirectory(path) 103 YuzuApplication.documentsTree!!.isDirectory(path)
103 } else { 104 } else {
104 FileUtil.isDirectory(path) 105 FileUtil.isDirectory(path)
105 } 106 }
106 } 107 }
107 108
109 @Keep
110 @JvmStatic
111 fun getParentDirectory(path: String): String =
112 if (DocumentsTree.isNativePath(path)) {
113 YuzuApplication.documentsTree!!.getParentDirectory(path)
114 } else {
115 path
116 }
117
118 @Keep
119 @JvmStatic
120 fun getFilename(path: String): String =
121 if (DocumentsTree.isNativePath(path)) {
122 YuzuApplication.documentsTree!!.getFilename(path)
123 } else {
124 FileUtil.getFilename(Uri.parse(path))
125 }
126
108 /** 127 /**
109 * Returns true if pro controller isn't available and handheld is 128 * Returns true if pro controller isn't available and handheld is
110 */ 129 */
@@ -215,32 +234,6 @@ object NativeLibrary {
215 234
216 external fun initGameIni(gameID: String?) 235 external fun initGameIni(gameID: String?)
217 236
218 /**
219 * Gets the embedded icon within the given ROM.
220 *
221 * @param filename the file path to the ROM.
222 * @return a byte array containing the JPEG data for the icon.
223 */
224 external fun getIcon(filename: String): ByteArray
225
226 /**
227 * Gets the embedded title of the given ISO/ROM.
228 *
229 * @param filename The file path to the ISO/ROM.
230 * @return the embedded title of the ISO/ROM.
231 */
232 external fun getTitle(filename: String): String
233
234 external fun getDescription(filename: String): String
235
236 external fun getGameId(filename: String): String
237
238 external fun getRegions(filename: String): String
239
240 external fun getCompany(filename: String): String
241
242 external fun isHomebrew(filename: String): Boolean
243
244 external fun setAppDirectory(directory: String) 237 external fun setAppDirectory(directory: String)
245 238
246 /** 239 /**
@@ -294,11 +287,6 @@ object NativeLibrary {
294 external fun stopEmulation() 287 external fun stopEmulation()
295 288
296 /** 289 /**
297 * Resets the in-memory ROM metadata cache.
298 */
299 external fun resetRomMetadata()
300
301 /**
302 * Returns true if emulation is running (or is paused). 290 * Returns true if emulation is running (or is paused).
303 */ 291 */
304 external fun isRunning(): Boolean 292 external fun isRunning(): Boolean
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
index f9f88a1d2..0c82cdba8 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt
@@ -147,7 +147,7 @@ class GameAdapter(private val activity: AppCompatActivity) :
147 147
148 private class DiffCallback : DiffUtil.ItemCallback<Game>() { 148 private class DiffCallback : DiffUtil.ItemCallback<Game>() {
149 override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { 149 override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean {
150 return oldItem.gameId == newItem.gameId 150 return oldItem.programId == newItem.programId
151 } 151 }
152 152
153 override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean { 153 override fun areContentsTheSame(oldItem: Game, newItem: Game): Boolean {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
index 6527c64ab..b43978fce 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Game.kt
@@ -12,15 +12,14 @@ import kotlinx.serialization.Serializable
12@Serializable 12@Serializable
13class Game( 13class Game(
14 val title: String, 14 val title: String,
15 val description: String,
16 val regions: String,
17 val path: String, 15 val path: String,
18 val gameId: String, 16 val programId: String,
19 val company: String, 17 val developer: String,
18 val version: String,
20 val isHomebrew: Boolean 19 val isHomebrew: Boolean
21) : Parcelable { 20) : Parcelable {
22 val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" 21 val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime"
23 val keyLastPlayedTime get() = "${gameId}_LastPlayed" 22 val keyLastPlayedTime get() = "${programId}_LastPlayed"
24 23
25 override fun equals(other: Any?): Boolean { 24 override fun equals(other: Any?): Boolean {
26 if (other !is Game) { 25 if (other !is Game) {
@@ -32,11 +31,9 @@ class Game(
32 31
33 override fun hashCode(): Int { 32 override fun hashCode(): Int {
34 var result = title.hashCode() 33 var result = title.hashCode()
35 result = 31 * result + description.hashCode()
36 result = 31 * result + regions.hashCode()
37 result = 31 * result + path.hashCode() 34 result = 31 * result + path.hashCode()
38 result = 31 * result + gameId.hashCode() 35 result = 31 * result + programId.hashCode()
39 result = 31 * result + company.hashCode() 36 result = 31 * result + developer.hashCode()
40 result = 31 * result + isHomebrew.hashCode() 37 result = 31 * result + isHomebrew.hashCode()
41 return result 38 return result
42 } 39 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
index 004b25b04..8512ed17c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/GamesViewModel.kt
@@ -14,15 +14,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
14import kotlinx.coroutines.flow.StateFlow 14import kotlinx.coroutines.flow.StateFlow
15import kotlinx.coroutines.launch 15import kotlinx.coroutines.launch
16import kotlinx.coroutines.withContext 16import kotlinx.coroutines.withContext
17import kotlinx.serialization.ExperimentalSerializationApi
18import kotlinx.serialization.MissingFieldException
19import kotlinx.serialization.decodeFromString 17import kotlinx.serialization.decodeFromString
20import kotlinx.serialization.json.Json 18import kotlinx.serialization.json.Json
21import org.yuzu.yuzu_emu.NativeLibrary 19import org.yuzu.yuzu_emu.NativeLibrary
22import org.yuzu.yuzu_emu.YuzuApplication 20import org.yuzu.yuzu_emu.YuzuApplication
23import org.yuzu.yuzu_emu.utils.GameHelper 21import org.yuzu.yuzu_emu.utils.GameHelper
22import org.yuzu.yuzu_emu.utils.GameMetadata
24 23
25@OptIn(ExperimentalSerializationApi::class)
26class GamesViewModel : ViewModel() { 24class GamesViewModel : ViewModel() {
27 val games: StateFlow<List<Game>> get() = _games 25 val games: StateFlow<List<Game>> get() = _games
28 private val _games = MutableStateFlow(emptyList<Game>()) 26 private val _games = MutableStateFlow(emptyList<Game>())
@@ -58,7 +56,8 @@ class GamesViewModel : ViewModel() {
58 val game: Game 56 val game: Game
59 try { 57 try {
60 game = Json.decodeFromString(it) 58 game = Json.decodeFromString(it)
61 } catch (e: MissingFieldException) { 59 } catch (e: Exception) {
60 // We don't care about any errors related to parsing the game cache
62 return@forEach 61 return@forEach
63 } 62 }
64 63
@@ -113,7 +112,7 @@ class GamesViewModel : ViewModel() {
113 112
114 viewModelScope.launch { 113 viewModelScope.launch {
115 withContext(Dispatchers.IO) { 114 withContext(Dispatchers.IO) {
116 NativeLibrary.resetRomMetadata() 115 GameMetadata.resetMetadata()
117 setGames(GameHelper.getGames()) 116 setGames(GameHelper.getGames())
118 _isReloading.value = false 117 _isReloading.value = false
119 118
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 {
42 return node != null && node.isDirectory 42 return node != null && node.isDirectory
43 } 43 }
44 44
45 fun getParentDirectory(filepath: String): String {
46 val node = resolvePath(filepath)!!
47 val parentNode = node.parent
48 if (parentNode != null && parentNode.isDirectory) {
49 return parentNode.uri!!.toString()
50 }
51 return node.uri!!.toString()
52 }
53
54 fun getFilename(filepath: String): String {
55 val node = resolvePath(filepath)
56 if (node != null) {
57 return node.name!!
58 }
59 return filepath
60 }
61
45 private fun resolvePath(filepath: String): DocumentsNode? { 62 private fun resolvePath(filepath: String): DocumentsNode? {
46 val tokens = StringTokenizer(filepath, File.separator, false) 63 val tokens = StringTokenizer(filepath, File.separator, false)
47 var iterator = root 64 var iterator = root
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
index 5ee74a52c..8c3268e9c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
@@ -144,7 +144,7 @@ object FileUtil {
144 * @param path Native content uri path 144 * @param path Native content uri path
145 * @return bool 145 * @return bool
146 */ 146 */
147 fun exists(path: String?): Boolean { 147 fun exists(path: String?, suppressLog: Boolean = false): Boolean {
148 var c: Cursor? = null 148 var c: Cursor? = null
149 try { 149 try {
150 val mUri = Uri.parse(path) 150 val mUri = Uri.parse(path)
@@ -152,7 +152,9 @@ object FileUtil {
152 c = context.contentResolver.query(mUri, columns, null, null, null) 152 c = context.contentResolver.query(mUri, columns, null, null, null)
153 return c!!.count > 0 153 return c!!.count > 0
154 } catch (e: Exception) { 154 } catch (e: Exception) {
155 Log.info("[FileUtil] Cannot find file from given path, error: " + e.message) 155 if (!suppressLog) {
156 Log.info("[FileUtil] Cannot find file from given path, error: " + e.message)
157 }
156 } finally { 158 } finally {
157 closeQuietly(c) 159 closeQuietly(c)
158 } 160 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
index 9001ca9ab..e6aca6b44 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
@@ -71,27 +71,26 @@ object GameHelper {
71 71
72 fun getGame(uri: Uri, addedToLibrary: Boolean): Game { 72 fun getGame(uri: Uri, addedToLibrary: Boolean): Game {
73 val filePath = uri.toString() 73 val filePath = uri.toString()
74 var name = NativeLibrary.getTitle(filePath) 74 var name = GameMetadata.getTitle(filePath)
75 75
76 // If the game's title field is empty, use the filename. 76 // If the game's title field is empty, use the filename.
77 if (name.isEmpty()) { 77 if (name.isEmpty()) {
78 name = FileUtil.getFilename(uri) 78 name = FileUtil.getFilename(uri)
79 } 79 }
80 var gameId = NativeLibrary.getGameId(filePath) 80 var programId = GameMetadata.getProgramId(filePath)
81 81
82 // If the game's ID field is empty, use the filename without extension. 82 // If the game's ID field is empty, use the filename without extension.
83 if (gameId.isEmpty()) { 83 if (programId.isEmpty()) {
84 gameId = name.substring(0, name.lastIndexOf(".")) 84 programId = name.substring(0, name.lastIndexOf("."))
85 } 85 }
86 86
87 val newGame = Game( 87 val newGame = Game(
88 name, 88 name,
89 NativeLibrary.getDescription(filePath).replace("\n", " "),
90 NativeLibrary.getRegions(filePath),
91 filePath, 89 filePath,
92 gameId, 90 programId,
93 NativeLibrary.getCompany(filePath), 91 GameMetadata.getDeveloper(filePath),
94 NativeLibrary.isHomebrew(filePath) 92 GameMetadata.getVersion(filePath),
93 GameMetadata.getIsHomebrew(filePath)
95 ) 94 )
96 95
97 if (addedToLibrary) { 96 if (addedToLibrary) {
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
index 9fe99fab1..654d62f52 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameIconUtils.kt
@@ -18,7 +18,6 @@ import coil.key.Keyer
18import coil.memory.MemoryCache 18import coil.memory.MemoryCache
19import coil.request.ImageRequest 19import coil.request.ImageRequest
20import coil.request.Options 20import coil.request.Options
21import org.yuzu.yuzu_emu.NativeLibrary
22import org.yuzu.yuzu_emu.R 21import org.yuzu.yuzu_emu.R
23import org.yuzu.yuzu_emu.YuzuApplication 22import org.yuzu.yuzu_emu.YuzuApplication
24import org.yuzu.yuzu_emu.model.Game 23import org.yuzu.yuzu_emu.model.Game
@@ -36,7 +35,7 @@ class GameIconFetcher(
36 } 35 }
37 36
38 private fun decodeGameIcon(uri: String): Bitmap? { 37 private fun decodeGameIcon(uri: String): Bitmap? {
39 val data = NativeLibrary.getIcon(uri) 38 val data = GameMetadata.getIcon(uri)
40 return BitmapFactory.decodeByteArray( 39 return BitmapFactory.decodeByteArray(
41 data, 40 data,
42 0, 41 0,
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt
new file mode 100644
index 000000000..0f3542ac6
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt
@@ -0,0 +1,20 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6object GameMetadata {
7 external fun getTitle(path: String): String
8
9 external fun getProgramId(path: String): String
10
11 external fun getDeveloper(path: String): String
12
13 external fun getVersion(path: String): String
14
15 external fun getIcon(path: String): ByteArray
16
17 external fun getIsHomebrew(path: String): Boolean
18
19 external fun resetMetadata()
20}
diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt
index e15d1480b..1c36661f5 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -14,8 +14,10 @@ add_library(yuzu-android SHARED
14 id_cache.cpp 14 id_cache.cpp
15 id_cache.h 15 id_cache.h
16 native.cpp 16 native.cpp
17 native.h
17 native_config.cpp 18 native_config.cpp
18 uisettings.cpp 19 uisettings.cpp
20 game_metadata.cpp
19) 21)
20 22
21set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR}) 23set_property(TARGET yuzu-android PROPERTY IMPORTED_LOCATION ${FFmpeg_LIBRARY_DIR})
diff --git a/src/android/app/src/main/jni/game_metadata.cpp b/src/android/app/src/main/jni/game_metadata.cpp
new file mode 100644
index 000000000..24d9df702
--- /dev/null
+++ b/src/android/app/src/main/jni/game_metadata.cpp
@@ -0,0 +1,112 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <core/core.h>
5#include <core/file_sys/patch_manager.h>
6#include <core/loader/nro.h>
7#include <jni.h>
8#include "core/loader/loader.h"
9#include "jni/android_common/android_common.h"
10#include "native.h"
11
12struct RomMetadata {
13 std::string title;
14 u64 programId;
15 std::string developer;
16 std::string version;
17 std::vector<u8> icon;
18 bool isHomebrew;
19};
20
21std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
22
23RomMetadata CacheRomMetadata(const std::string& path) {
24 const auto file =
25 Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
26 auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
27
28 RomMetadata entry;
29 loader->ReadTitle(entry.title);
30 loader->ReadProgramId(entry.programId);
31 loader->ReadIcon(entry.icon);
32
33 const FileSys::PatchManager pm{
34 entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
35 EmulationSession::GetInstance().System().GetContentProvider()};
36 const auto control = pm.GetControlMetadata();
37
38 if (control.first != nullptr) {
39 entry.developer = control.first->GetDeveloperName();
40 entry.version = control.first->GetVersionString();
41 } else {
42 FileSys::NACP nacp;
43 if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
44 entry.developer = nacp.GetDeveloperName();
45 } else {
46 entry.developer = "";
47 }
48
49 entry.version = "1.0.0";
50 }
51
52 if (loader->GetFileType() == Loader::FileType::NRO) {
53 auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
54 entry.isHomebrew = loader_nro->IsHomebrew();
55 } else {
56 entry.isHomebrew = false;
57 }
58
59 m_rom_metadata_cache[path] = entry;
60
61 return entry;
62}
63
64RomMetadata GetRomMetadata(const std::string& path) {
65 if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) {
66 return search->second;
67 }
68
69 return CacheRomMetadata(path);
70}
71
72extern "C" {
73
74jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj,
75 jstring jpath) {
76 return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title);
77}
78
79jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj,
80 jstring jpath) {
81 return ToJString(env, std::to_string(GetRomMetadata(GetJString(env, jpath)).programId));
82}
83
84jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj,
85 jstring jpath) {
86 return ToJString(env, GetRomMetadata(GetJString(env, jpath)).developer);
87}
88
89jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj,
90 jstring jpath) {
91 return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version);
92}
93
94jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj,
95 jstring jpath) {
96 auto icon_data = GetRomMetadata(GetJString(env, jpath)).icon;
97 jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
98 env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
99 reinterpret_cast<jbyte*>(icon_data.data()));
100 return icon;
101}
102
103jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj,
104 jstring jpath) {
105 return static_cast<jboolean>(GetRomMetadata(GetJString(env, jpath)).isHomebrew);
106}
107
108void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) {
109 return m_rom_metadata_cache.clear();
110}
111
112} // extern "C"
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 598f4e8bf..686b73588 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -33,7 +33,6 @@
33#include "core/crypto/key_manager.h" 33#include "core/crypto/key_manager.h"
34#include "core/file_sys/card_image.h" 34#include "core/file_sys/card_image.h"
35#include "core/file_sys/content_archive.h" 35#include "core/file_sys/content_archive.h"
36#include "core/file_sys/registered_cache.h"
37#include "core/file_sys/submission_package.h" 36#include "core/file_sys/submission_package.h"
38#include "core/file_sys/vfs.h" 37#include "core/file_sys/vfs.h"
39#include "core/file_sys/vfs_real.h" 38#include "core/file_sys/vfs_real.h"
@@ -48,514 +47,416 @@
48#include "core/hid/emulated_controller.h" 47#include "core/hid/emulated_controller.h"
49#include "core/hid/hid_core.h" 48#include "core/hid/hid_core.h"
50#include "core/hid/hid_types.h" 49#include "core/hid/hid_types.h"
51#include "core/hle/service/acc/profile_manager.h"
52#include "core/hle/service/am/applet_ae.h" 50#include "core/hle/service/am/applet_ae.h"
53#include "core/hle/service/am/applet_oe.h" 51#include "core/hle/service/am/applet_oe.h"
54#include "core/hle/service/am/applets/applets.h" 52#include "core/hle/service/am/applets/applets.h"
55#include "core/hle/service/filesystem/filesystem.h" 53#include "core/hle/service/filesystem/filesystem.h"
56#include "core/loader/loader.h" 54#include "core/loader/loader.h"
57#include "core/perf_stats.h"
58#include "jni/android_common/android_common.h" 55#include "jni/android_common/android_common.h"
59#include "jni/applets/software_keyboard.h"
60#include "jni/config.h" 56#include "jni/config.h"
61#include "jni/emu_window/emu_window.h"
62#include "jni/id_cache.h" 57#include "jni/id_cache.h"
63#include "video_core/rasterizer_interface.h" 58#include "jni/native.h"
64#include "video_core/renderer_base.h" 59#include "video_core/renderer_base.h"
65 60
66#define jconst [[maybe_unused]] const auto 61#define jconst [[maybe_unused]] const auto
67#define jauto [[maybe_unused]] auto 62#define jauto [[maybe_unused]] auto
68 63
69namespace { 64static EmulationSession s_instance;
70 65
71class EmulationSession final { 66EmulationSession::EmulationSession() {
72public: 67 m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
73 EmulationSession() { 68}
74 m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
75 }
76
77 ~EmulationSession() = default;
78
79 static EmulationSession& GetInstance() {
80 return s_instance;
81 }
82
83 const Core::System& System() const {
84 return m_system;
85 }
86 69
87 Core::System& System() { 70EmulationSession& EmulationSession::GetInstance() {
88 return m_system; 71 return s_instance;
89 } 72}
90 73
91 const EmuWindow_Android& Window() const { 74const Core::System& EmulationSession::System() const {
92 return *m_window; 75 return m_system;
93 } 76}
94 77
95 EmuWindow_Android& Window() { 78Core::System& EmulationSession::System() {
96 return *m_window; 79 return m_system;
97 } 80}
98 81
99 ANativeWindow* NativeWindow() const { 82const EmuWindow_Android& EmulationSession::Window() const {
100 return m_native_window; 83 return *m_window;
101 } 84}
102 85
103 void SetNativeWindow(ANativeWindow* native_window) { 86EmuWindow_Android& EmulationSession::Window() {
104 m_native_window = native_window; 87 return *m_window;
105 } 88}
106 89
107 int InstallFileToNand(std::string filename, std::string file_extension) { 90ANativeWindow* EmulationSession::NativeWindow() const {
108 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest, 91 return m_native_window;
109 std::size_t block_size) { 92}
110 if (src == nullptr || dest == nullptr) {
111 return false;
112 }
113 if (!dest->Resize(src->GetSize())) {
114 return false;
115 }
116 93
117 using namespace Common::Literals; 94void EmulationSession::SetNativeWindow(ANativeWindow* native_window) {
118 [[maybe_unused]] std::vector<u8> buffer(1_MiB); 95 m_native_window = native_window;
96}
119 97
120 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) { 98int EmulationSession::InstallFileToNand(std::string filename, std::string file_extension) {
121 jconst read = src->Read(buffer.data(), buffer.size(), i); 99 jconst copy_func = [](const FileSys::VirtualFile& src, const FileSys::VirtualFile& dest,
122 dest->Write(buffer.data(), read, i); 100 std::size_t block_size) {
123 } 101 if (src == nullptr || dest == nullptr) {
124 return true; 102 return false;
125 };
126
127 enum InstallResult {
128 Success = 0,
129 SuccessFileOverwritten = 1,
130 InstallError = 2,
131 ErrorBaseGame = 3,
132 ErrorFilenameExtension = 4,
133 };
134
135 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
136 m_system.GetFileSystemController().CreateFactories(*m_vfs);
137
138 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
139 if (file_extension == "nsp") {
140 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
141 if (nsp->IsExtractedType()) {
142 return InstallError;
143 }
144 } else {
145 return ErrorFilenameExtension;
146 } 103 }
147 104 if (!dest->Resize(src->GetSize())) {
148 if (!nsp) { 105 return false;
149 return InstallError;
150 } 106 }
151 107
152 if (nsp->GetStatus() != Loader::ResultStatus::Success) { 108 using namespace Common::Literals;
153 return InstallError; 109 [[maybe_unused]] std::vector<u8> buffer(1_MiB);
154 }
155 110
156 jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry( 111 for (std::size_t i = 0; i < src->GetSize(); i += buffer.size()) {
157 *nsp, true, copy_func); 112 jconst read = src->Read(buffer.data(), buffer.size(), i);
158 113 dest->Write(buffer.data(), read, i);
159 switch (res) {
160 case FileSys::InstallResult::Success:
161 return Success;
162 case FileSys::InstallResult::OverwriteExisting:
163 return SuccessFileOverwritten;
164 case FileSys::InstallResult::ErrorBaseInstall:
165 return ErrorBaseGame;
166 default:
167 return InstallError;
168 } 114 }
169 } 115 return true;
116 };
170 117
171 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir, 118 enum InstallResult {
172 const std::string& custom_driver_name, 119 Success = 0,
173 const std::string& file_redirect_dir) { 120 SuccessFileOverwritten = 1,
174#ifdef ARCHITECTURE_arm64 121 InstallError = 2,
175 void* handle{}; 122 ErrorBaseGame = 3,
176 const char* file_redirect_dir_{}; 123 ErrorFilenameExtension = 4,
177 int featureFlags{}; 124 };
178
179 // Enable driver file redirection when renderer debugging is enabled.
180 if (Settings::values.renderer_debug && file_redirect_dir.size()) {
181 featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
182 file_redirect_dir_ = file_redirect_dir.c_str();
183 }
184 125
185 // Try to load a custom driver. 126 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
186 if (custom_driver_name.size()) { 127 m_system.GetFileSystemController().CreateFactories(*m_vfs);
187 handle = adrenotools_open_libvulkan(
188 RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
189 custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
190 }
191 128
192 // Try to load the system driver. 129 [[maybe_unused]] std::shared_ptr<FileSys::NSP> nsp;
193 if (!handle) { 130 if (file_extension == "nsp") {
194 handle = 131 nsp = std::make_shared<FileSys::NSP>(m_vfs->OpenFile(filename, FileSys::Mode::Read));
195 adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(), 132 if (nsp->IsExtractedType()) {
196 nullptr, nullptr, file_redirect_dir_, nullptr); 133 return InstallError;
197 } 134 }
198 135 } else {
199 m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle); 136 return ErrorFilenameExtension;
200#endif
201 } 137 }
202 138
203 bool IsRunning() const { 139 if (!nsp) {
204 return m_is_running; 140 return InstallError;
205 } 141 }
206 142
207 bool IsPaused() const { 143 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
208 return m_is_running && m_is_paused; 144 return InstallError;
209 } 145 }
210 146
211 const Core::PerfStatsResults& PerfStats() const { 147 jconst res = m_system.GetFileSystemController().GetUserNANDContents()->InstallEntry(*nsp, true,
212 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex); 148 copy_func);
213 return m_perf_stats;
214 }
215 149
216 void SurfaceChanged() { 150 switch (res) {
217 if (!IsRunning()) { 151 case FileSys::InstallResult::Success:
218 return; 152 return Success;
219 } 153 case FileSys::InstallResult::OverwriteExisting:
220 m_window->OnSurfaceChanged(m_native_window); 154 return SuccessFileOverwritten;
155 case FileSys::InstallResult::ErrorBaseInstall:
156 return ErrorBaseGame;
157 default:
158 return InstallError;
221 } 159 }
160}
222 161
223 void ConfigureFilesystemProvider(const std::string& filepath) { 162void EmulationSession::InitializeGpuDriver(const std::string& hook_lib_dir,
224 const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read); 163 const std::string& custom_driver_dir,
225 if (!file) { 164 const std::string& custom_driver_name,
226 return; 165 const std::string& file_redirect_dir) {
227 } 166#ifdef ARCHITECTURE_arm64
228 167 void* handle{};
229 auto loader = Loader::GetLoader(m_system, file); 168 const char* file_redirect_dir_{};
230 if (!loader) { 169 int featureFlags{};
231 return;
232 }
233
234 const auto file_type = loader->GetFileType();
235 if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
236 return;
237 }
238 170
239 u64 program_id = 0; 171 // Enable driver file redirection when renderer debugging is enabled.
240 const auto res2 = loader->ReadProgramId(program_id); 172 if (Settings::values.renderer_debug && file_redirect_dir.size()) {
241 if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) { 173 featureFlags |= ADRENOTOOLS_DRIVER_FILE_REDIRECT;
242 m_manual_provider->AddEntry(FileSys::TitleType::Application, 174 file_redirect_dir_ = file_redirect_dir.c_str();
243 FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
244 program_id, file);
245 } else if (res2 == Loader::ResultStatus::Success &&
246 (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
247 const auto nsp = file_type == Loader::FileType::NSP
248 ? std::make_shared<FileSys::NSP>(file)
249 : FileSys::XCI{file}.GetSecurePartitionNSP();
250 for (const auto& title : nsp->GetNCAs()) {
251 for (const auto& entry : title.second) {
252 m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
253 entry.second->GetBaseFile());
254 }
255 }
256 }
257 } 175 }
258 176
259 Core::SystemResultStatus InitializeEmulation(const std::string& filepath) { 177 // Try to load a custom driver.
260 std::scoped_lock lock(m_mutex); 178 if (custom_driver_name.size()) {
261 179 handle = adrenotools_open_libvulkan(
262 // Create the render window. 180 RTLD_NOW, featureFlags | ADRENOTOOLS_DRIVER_CUSTOM, nullptr, hook_lib_dir.c_str(),
263 m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, 181 custom_driver_dir.c_str(), custom_driver_name.c_str(), file_redirect_dir_, nullptr);
264 m_vulkan_library); 182 }
265
266 m_system.SetFilesystem(m_vfs);
267 m_system.GetUserChannel().clear();
268
269 // Initialize system.
270 jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
271 m_software_keyboard = android_keyboard.get();
272 m_system.SetShuttingDown(false);
273 m_system.ApplySettings();
274 Settings::LogSettings();
275 m_system.HIDCore().ReloadInputDevices();
276 m_system.SetAppletFrontendSet({
277 nullptr, // Amiibo Settings
278 nullptr, // Controller Selector
279 nullptr, // Error Display
280 nullptr, // Mii Editor
281 nullptr, // Parental Controls
282 nullptr, // Photo Viewer
283 nullptr, // Profile Selector
284 std::move(android_keyboard), // Software Keyboard
285 nullptr, // Web Browser
286 });
287
288 // Initialize filesystem.
289 m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
290 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
291 m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
292 m_manual_provider.get());
293 m_system.GetFileSystemController().CreateFactories(*m_vfs);
294 ConfigureFilesystemProvider(filepath);
295
296 // Initialize account manager
297 m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
298
299 // Load the ROM.
300 m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
301 if (m_load_result != Core::SystemResultStatus::Success) {
302 return m_load_result;
303 }
304
305 // Complete initialization.
306 m_system.GPU().Start();
307 m_system.GetCpuManager().OnGpuReady();
308 m_system.RegisterExitCallback([&] { HaltEmulation(); });
309 183
310 return Core::SystemResultStatus::Success; 184 // Try to load the system driver.
185 if (!handle) {
186 handle = adrenotools_open_libvulkan(RTLD_NOW, featureFlags, nullptr, hook_lib_dir.c_str(),
187 nullptr, nullptr, file_redirect_dir_, nullptr);
311 } 188 }
312 189
313 void ShutdownEmulation() { 190 m_vulkan_library = std::make_shared<Common::DynamicLibrary>(handle);
314 std::scoped_lock lock(m_mutex); 191#endif
192}
315 193
316 m_is_running = false; 194bool EmulationSession::IsRunning() const {
195 return m_is_running;
196}
317 197
318 // Unload user input. 198bool EmulationSession::IsPaused() const {
319 m_system.HIDCore().UnloadInputDevices(); 199 return m_is_running && m_is_paused;
200}
320 201
321 // Shutdown the main emulated process 202const Core::PerfStatsResults& EmulationSession::PerfStats() const {
322 if (m_load_result == Core::SystemResultStatus::Success) { 203 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
323 m_system.DetachDebugger(); 204 return m_perf_stats;
324 m_system.ShutdownMainProcess(); 205}
325 m_detached_tasks.WaitForAllTasks();
326 m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
327 m_window.reset();
328 OnEmulationStopped(Core::SystemResultStatus::Success);
329 return;
330 }
331 206
332 // Tear down the render window. 207void EmulationSession::SurfaceChanged() {
333 m_window.reset(); 208 if (!IsRunning()) {
209 return;
334 } 210 }
211 m_window->OnSurfaceChanged(m_native_window);
212}
335 213
336 void PauseEmulation() { 214void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
337 std::scoped_lock lock(m_mutex); 215 const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::Mode::Read);
338 m_system.Pause(); 216 if (!file) {
339 m_is_paused = true; 217 return;
340 } 218 }
341 219
342 void UnPauseEmulation() { 220 auto loader = Loader::GetLoader(m_system, file);
343 std::scoped_lock lock(m_mutex); 221 if (!loader) {
344 m_system.Run(); 222 return;
345 m_is_paused = false;
346 } 223 }
347 224
348 void HaltEmulation() { 225 const auto file_type = loader->GetFileType();
349 std::scoped_lock lock(m_mutex); 226 if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
350 m_is_running = false; 227 return;
351 m_cv.notify_one();
352 } 228 }
353 229
354 void RunEmulation() { 230 u64 program_id = 0;
355 { 231 const auto res2 = loader->ReadProgramId(program_id);
356 std::scoped_lock lock(m_mutex); 232 if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
357 m_is_running = true; 233 m_manual_provider->AddEntry(FileSys::TitleType::Application,
358 } 234 FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
359 235 program_id, file);
360 // Load the disk shader cache. 236 } else if (res2 == Loader::ResultStatus::Success &&
361 if (Settings::values.use_disk_shader_cache.GetValue()) { 237 (file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
362 LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); 238 const auto nsp = file_type == Loader::FileType::NSP
363 m_system.Renderer().ReadRasterizer()->LoadDiskResources( 239 ? std::make_shared<FileSys::NSP>(file)
364 m_system.GetApplicationProcessProgramID(), std::stop_token{}, 240 : FileSys::XCI{file}.GetSecurePartitionNSP();
365 LoadDiskCacheProgress); 241 for (const auto& title : nsp->GetNCAs()) {
366 LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); 242 for (const auto& entry : title.second) {
243 m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
244 entry.second->GetBaseFile());
245 }
367 } 246 }
247 }
248}
368 249
369 void(m_system.Run()); 250Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath) {
370 251 std::scoped_lock lock(m_mutex);
371 if (m_system.DebuggerEnabled()) { 252
372 m_system.InitializeDebugger(); 253 // Create the render window.
373 } 254 m_window =
255 std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window, m_vulkan_library);
256
257 m_system.SetFilesystem(m_vfs);
258 m_system.GetUserChannel().clear();
259
260 // Initialize system.
261 jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
262 m_software_keyboard = android_keyboard.get();
263 m_system.SetShuttingDown(false);
264 m_system.ApplySettings();
265 Settings::LogSettings();
266 m_system.HIDCore().ReloadInputDevices();
267 m_system.SetAppletFrontendSet({
268 nullptr, // Amiibo Settings
269 nullptr, // Controller Selector
270 nullptr, // Error Display
271 nullptr, // Mii Editor
272 nullptr, // Parental Controls
273 nullptr, // Photo Viewer
274 nullptr, // Profile Selector
275 std::move(android_keyboard), // Software Keyboard
276 nullptr, // Web Browser
277 });
278
279 // Initialize filesystem.
280 m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
281 m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
282 m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
283 m_manual_provider.get());
284 m_system.GetFileSystemController().CreateFactories(*m_vfs);
285 ConfigureFilesystemProvider(filepath);
286
287 // Initialize account manager
288 m_profile_manager = std::make_unique<Service::Account::ProfileManager>();
289
290 // Load the ROM.
291 m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath);
292 if (m_load_result != Core::SystemResultStatus::Success) {
293 return m_load_result;
294 }
295
296 // Complete initialization.
297 m_system.GPU().Start();
298 m_system.GetCpuManager().OnGpuReady();
299 m_system.RegisterExitCallback([&] { HaltEmulation(); });
374 300
375 OnEmulationStarted(); 301 return Core::SystemResultStatus::Success;
302}
376 303
377 while (true) { 304void EmulationSession::ShutdownEmulation() {
378 { 305 std::scoped_lock lock(m_mutex);
379 [[maybe_unused]] std::unique_lock lock(m_mutex);
380 if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
381 [&]() { return !m_is_running; })) {
382 // Emulation halted.
383 break;
384 }
385 }
386 {
387 // Refresh performance stats.
388 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
389 m_perf_stats = m_system.GetAndResetPerfStats();
390 }
391 }
392 }
393 306
394 std::string GetRomTitle(const std::string& path) { 307 m_is_running = false;
395 return GetRomMetadata(path).title;
396 }
397 308
398 std::vector<u8> GetRomIcon(const std::string& path) { 309 // Unload user input.
399 return GetRomMetadata(path).icon; 310 m_system.HIDCore().UnloadInputDevices();
400 }
401 311
402 bool GetIsHomebrew(const std::string& path) { 312 // Shutdown the main emulated process
403 return GetRomMetadata(path).isHomebrew; 313 if (m_load_result == Core::SystemResultStatus::Success) {
314 m_system.DetachDebugger();
315 m_system.ShutdownMainProcess();
316 m_detached_tasks.WaitForAllTasks();
317 m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
318 m_window.reset();
319 OnEmulationStopped(Core::SystemResultStatus::Success);
320 return;
404 } 321 }
405 322
406 void ResetRomMetadata() { 323 // Tear down the render window.
407 m_rom_metadata_cache.clear(); 324 m_window.reset();
408 } 325}
409 326
410 bool IsHandheldOnly() { 327void EmulationSession::PauseEmulation() {
411 jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag(); 328 std::scoped_lock lock(m_mutex);
329 m_system.Pause();
330 m_is_paused = true;
331}
412 332
413 if (npad_style_set.fullkey == 1) { 333void EmulationSession::UnPauseEmulation() {
414 return false; 334 std::scoped_lock lock(m_mutex);
415 } 335 m_system.Run();
336 m_is_paused = false;
337}
416 338
417 if (npad_style_set.handheld == 0) { 339void EmulationSession::HaltEmulation() {
418 return false; 340 std::scoped_lock lock(m_mutex);
419 } 341 m_is_running = false;
342 m_cv.notify_one();
343}
420 344
421 return !Settings::IsDockedMode(); 345void EmulationSession::RunEmulation() {
346 {
347 std::scoped_lock lock(m_mutex);
348 m_is_running = true;
422 } 349 }
423 350
424 void SetDeviceType([[maybe_unused]] int index, int type) { 351 // Load the disk shader cache.
425 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); 352 if (Settings::values.use_disk_shader_cache.GetValue()) {
426 controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type)); 353 LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
354 m_system.Renderer().ReadRasterizer()->LoadDiskResources(
355 m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress);
356 LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
427 } 357 }
428 358
429 void OnGamepadConnectEvent([[maybe_unused]] int index) { 359 void(m_system.Run());
430 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
431
432 // Ensure that player1 is configured correctly and handheld disconnected
433 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
434 jauto handheld =
435 m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
436 360
437 if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) { 361 if (m_system.DebuggerEnabled()) {
438 handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); 362 m_system.InitializeDebugger();
439 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController); 363 }
440 handheld->Disconnect();
441 }
442 }
443 364
444 // Ensure that handheld is configured correctly and player 1 disconnected 365 OnEmulationStarted();
445 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
446 jauto player1 =
447 m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
448 366
449 if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) { 367 while (true) {
450 player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); 368 {
451 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld); 369 [[maybe_unused]] std::unique_lock lock(m_mutex);
452 player1->Disconnect(); 370 if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
371 [&]() { return !m_is_running; })) {
372 // Emulation halted.
373 break;
453 } 374 }
454 } 375 }
455 376 {
456 if (!controller->IsConnected()) { 377 // Refresh performance stats.
457 controller->Connect(); 378 std::scoped_lock m_perf_stats_lock(m_perf_stats_mutex);
379 m_perf_stats = m_system.GetAndResetPerfStats();
458 } 380 }
459 } 381 }
382}
383
384bool EmulationSession::IsHandheldOnly() {
385 jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
460 386
461 void OnGamepadDisconnectEvent([[maybe_unused]] int index) { 387 if (npad_style_set.fullkey == 1) {
462 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index); 388 return false;
463 controller->Disconnect();
464 } 389 }
465 390
466 SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard() { 391 if (npad_style_set.handheld == 0) {
467 return m_software_keyboard; 392 return false;
468 } 393 }
469 394
470private: 395 return !Settings::IsDockedMode();
471 struct RomMetadata { 396}
472 std::string title;
473 std::vector<u8> icon;
474 bool isHomebrew;
475 };
476 397
477 RomMetadata GetRomMetadata(const std::string& path) { 398void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
478 if (jauto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { 399 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
479 return search->second; 400 controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
480 } 401}
481 402
482 return CacheRomMetadata(path); 403void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
483 } 404 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
484 405
485 RomMetadata CacheRomMetadata(const std::string& path) { 406 // Ensure that player1 is configured correctly and handheld disconnected
486 jconst file = Core::GetGameFileFromPath(m_vfs, path); 407 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
487 jauto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); 408 jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
488 409
489 RomMetadata entry; 410 if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
490 loader->ReadTitle(entry.title); 411 handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
491 loader->ReadIcon(entry.icon); 412 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::ProController);
492 if (loader->GetFileType() == Loader::FileType::NRO) { 413 handheld->Disconnect();
493 jauto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
494 entry.isHomebrew = loader_nro->IsHomebrew();
495 } else {
496 entry.isHomebrew = false;
497 } 414 }
498
499 m_rom_metadata_cache[path] = entry;
500
501 return entry;
502 } 415 }
503 416
504private: 417 // Ensure that handheld is configured correctly and player 1 disconnected
505 static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) { 418 if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
506 JNIEnv* env = IDCache::GetEnvForThread(); 419 jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
507 env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
508 IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
509 static_cast<jint>(progress), static_cast<jint>(max));
510 }
511 420
512 static void OnEmulationStarted() { 421 if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
513 JNIEnv* env = IDCache::GetEnvForThread(); 422 player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
514 env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), 423 controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
515 IDCache::GetOnEmulationStarted()); 424 player1->Disconnect();
425 }
516 } 426 }
517 427
518 static void OnEmulationStopped(Core::SystemResultStatus result) { 428 if (!controller->IsConnected()) {
519 JNIEnv* env = IDCache::GetEnvForThread(); 429 controller->Connect();
520 env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
521 IDCache::GetOnEmulationStopped(), static_cast<jint>(result));
522 } 430 }
431}
523 432
524private: 433void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
525 static EmulationSession s_instance; 434 jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
526 435 controller->Disconnect();
527 // Frontend management 436}
528 std::unordered_map<std::string, RomMetadata> m_rom_metadata_cache;
529
530 // Window management
531 std::unique_ptr<EmuWindow_Android> m_window;
532 ANativeWindow* m_native_window{};
533
534 // Core emulation
535 Core::System m_system;
536 InputCommon::InputSubsystem m_input_subsystem;
537 Common::DetachedTasks m_detached_tasks;
538 Core::PerfStatsResults m_perf_stats{};
539 std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
540 Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
541 std::atomic<bool> m_is_running = false;
542 std::atomic<bool> m_is_paused = false;
543 SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
544 std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
545 std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
546 437
547 // GPU driver parameters 438SoftwareKeyboard::AndroidKeyboard* EmulationSession::SoftwareKeyboard() {
548 std::shared_ptr<Common::DynamicLibrary> m_vulkan_library; 439 return m_software_keyboard;
440}
549 441
550 // Synchronization 442void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress,
551 std::condition_variable_any m_cv; 443 int max) {
552 mutable std::mutex m_perf_stats_mutex; 444 JNIEnv* env = IDCache::GetEnvForThread();
553 mutable std::mutex m_mutex; 445 env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(),
554}; 446 IDCache::GetDiskCacheLoadProgress(), static_cast<jint>(stage),
447 static_cast<jint>(progress), static_cast<jint>(max));
448}
555 449
556/*static*/ EmulationSession EmulationSession::s_instance; 450void EmulationSession::OnEmulationStarted() {
451 JNIEnv* env = IDCache::GetEnvForThread();
452 env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStarted());
453}
557 454
558} // Anonymous namespace 455void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
456 JNIEnv* env = IDCache::GetEnvForThread();
457 env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnEmulationStopped(),
458 static_cast<jint>(result));
459}
559 460
560static Core::SystemResultStatus RunEmulation(const std::string& filepath) { 461static Core::SystemResultStatus RunEmulation(const std::string& filepath) {
561 Common::Log::Initialize(); 462 Common::Log::Initialize();
@@ -657,10 +558,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass cla
657 EmulationSession::GetInstance().HaltEmulation(); 558 EmulationSession::GetInstance().HaltEmulation();
658} 559}
659 560
660void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
661 EmulationSession::GetInstance().ResetRomMetadata();
662}
663
664jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) { 561jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
665 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); 562 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
666} 563}
@@ -766,46 +663,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
766 } 663 }
767} 664}
768 665
769jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
770 jstring j_filename) {
771 jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
772 jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
773 env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
774 reinterpret_cast<jbyte*>(icon_data.data()));
775 return icon;
776}
777
778jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
779 jstring j_filename) {
780 jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
781 return env->NewStringUTF(title.c_str());
782}
783
784jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
785 jstring j_filename) {
786 return j_filename;
787}
788
789jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
790 jstring j_filename) {
791 return j_filename;
792}
793
794jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
795 jstring j_filename) {
796 return env->NewStringUTF("");
797}
798
799jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
800 jstring j_filename) {
801 return env->NewStringUTF("");
802}
803
804jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
805 jstring j_filename) {
806 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
807}
808
809void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) { 666void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
810 // Create the default config.ini. 667 // Create the default config.ini.
811 Config{}; 668 Config{};
diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h
new file mode 100644
index 000000000..b1db87e41
--- /dev/null
+++ b/src/android/app/src/main/jni/native.h
@@ -0,0 +1,84 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <android/native_window_jni.h>
5#include "common/detached_tasks.h"
6#include "core/core.h"
7#include "core/file_sys/registered_cache.h"
8#include "core/hle/service/acc/profile_manager.h"
9#include "core/perf_stats.h"
10#include "jni/applets/software_keyboard.h"
11#include "jni/emu_window/emu_window.h"
12#include "video_core/rasterizer_interface.h"
13
14#pragma once
15
16class EmulationSession final {
17public:
18 explicit EmulationSession();
19 ~EmulationSession() = default;
20
21 static EmulationSession& GetInstance();
22 const Core::System& System() const;
23 Core::System& System();
24
25 const EmuWindow_Android& Window() const;
26 EmuWindow_Android& Window();
27 ANativeWindow* NativeWindow() const;
28 void SetNativeWindow(ANativeWindow* native_window);
29 void SurfaceChanged();
30
31 int InstallFileToNand(std::string filename, std::string file_extension);
32 void InitializeGpuDriver(const std::string& hook_lib_dir, const std::string& custom_driver_dir,
33 const std::string& custom_driver_name,
34 const std::string& file_redirect_dir);
35
36 bool IsRunning() const;
37 bool IsPaused() const;
38 void PauseEmulation();
39 void UnPauseEmulation();
40 void HaltEmulation();
41 void RunEmulation();
42 void ShutdownEmulation();
43
44 const Core::PerfStatsResults& PerfStats() const;
45 void ConfigureFilesystemProvider(const std::string& filepath);
46 Core::SystemResultStatus InitializeEmulation(const std::string& filepath);
47
48 bool IsHandheldOnly();
49 void SetDeviceType([[maybe_unused]] int index, int type);
50 void OnGamepadConnectEvent([[maybe_unused]] int index);
51 void OnGamepadDisconnectEvent([[maybe_unused]] int index);
52 SoftwareKeyboard::AndroidKeyboard* SoftwareKeyboard();
53
54private:
55 static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
56 static void OnEmulationStarted();
57 static void OnEmulationStopped(Core::SystemResultStatus result);
58
59private:
60 // Window management
61 std::unique_ptr<EmuWindow_Android> m_window;
62 ANativeWindow* m_native_window{};
63
64 // Core emulation
65 Core::System m_system;
66 InputCommon::InputSubsystem m_input_subsystem;
67 Common::DetachedTasks m_detached_tasks;
68 Core::PerfStatsResults m_perf_stats{};
69 std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
70 Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
71 std::atomic<bool> m_is_running = false;
72 std::atomic<bool> m_is_paused = false;
73 SoftwareKeyboard::AndroidKeyboard* m_software_keyboard{};
74 std::unique_ptr<Service::Account::ProfileManager> m_profile_manager;
75 std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
76
77 // GPU driver parameters
78 std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
79
80 // Synchronization
81 std::condition_variable_any m_cv;
82 mutable std::mutex m_perf_stats_mutex;
83 mutable std::mutex m_mutex;
84};
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 @@
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include "common/fs/fs_android.h" 4#include "common/fs/fs_android.h"
5#include "common/string_util.h"
5 6
6namespace Common::FS::Android { 7namespace Common::FS::Android {
7 8
@@ -28,28 +29,35 @@ void RegisterCallbacks(JNIEnv* env, jclass clazz) {
28 env->GetJavaVM(&g_jvm); 29 env->GetJavaVM(&g_jvm);
29 native_library = clazz; 30 native_library = clazz;
30 31
32#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
33 F(JMethodID, JMethodName, Signature)
31#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ 34#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
32 F(JMethodID, JMethodName, Signature) 35 F(JMethodID, JMethodName, Signature)
33#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ 36#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
34 F(JMethodID, JMethodName, Signature) 37 F(JMethodID, JMethodName, Signature)
35#define F(JMethodID, JMethodName, Signature) \ 38#define F(JMethodID, JMethodName, Signature) \
36 JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature); 39 JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature);
40 ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
37 ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) 41 ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
38 ANDROID_STORAGE_FUNCTIONS(FS) 42 ANDROID_STORAGE_FUNCTIONS(FS)
39#undef F 43#undef F
40#undef FS 44#undef FS
41#undef FR 45#undef FR
46#undef FH
42} 47}
43 48
44void UnRegisterCallbacks() { 49void UnRegisterCallbacks() {
50#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
45#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) 51#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
46#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) 52#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
47#define F(JMethodID) JMethodID = nullptr; 53#define F(JMethodID) JMethodID = nullptr;
54 ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
48 ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) 55 ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
49 ANDROID_STORAGE_FUNCTIONS(FS) 56 ANDROID_STORAGE_FUNCTIONS(FS)
50#undef F 57#undef F
51#undef FS 58#undef FS
52#undef FR 59#undef FR
60#undef FH
53} 61}
54 62
55bool IsContentUri(const std::string& path) { 63bool IsContentUri(const std::string& path) {
@@ -95,4 +103,29 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
95#undef F 103#undef F
96#undef FR 104#undef FR
97 105
106#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) \
107 F(FunctionName, JMethodID, Caller)
108#define F(FunctionName, JMethodID, Caller) \
109 std::string FunctionName(const std::string& filepath) { \
110 if (JMethodID == nullptr) { \
111 return 0; \
112 } \
113 auto env = GetEnvForThread(); \
114 jstring j_filepath = env->NewStringUTF(filepath.c_str()); \
115 jstring j_return = \
116 static_cast<jstring>(env->Caller(native_library, JMethodID, j_filepath)); \
117 if (!j_return) { \
118 return {}; \
119 } \
120 const jchar* jchars = env->GetStringChars(j_return, nullptr); \
121 const jsize length = env->GetStringLength(j_return); \
122 const std::u16string_view string_view(reinterpret_cast<const char16_t*>(jchars), length); \
123 const std::string converted_string = Common::UTF16ToUTF8(string_view); \
124 env->ReleaseStringChars(j_return, jchars); \
125 return converted_string; \
126 }
127ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
128#undef F
129#undef FH
130
98} // namespace Common::FS::Android 131} // 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 @@
17 "(Ljava/lang/String;)Z") \ 17 "(Ljava/lang/String;)Z") \
18 V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z") 18 V(Exists, bool, file_exists, CallStaticBooleanMethod, "exists", "(Ljava/lang/String;)Z")
19 19
20#define ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(V) \
21 V(GetParentDirectory, get_parent_directory, CallStaticObjectMethod, "getParentDirectory", \
22 "(Ljava/lang/String;)Ljava/lang/String;") \
23 V(GetFilename, get_filename, CallStaticObjectMethod, "getFilename", \
24 "(Ljava/lang/String;)Ljava/lang/String;")
25
20namespace Common::FS::Android { 26namespace Common::FS::Android {
21 27
22static JavaVM* g_jvm = nullptr; 28static JavaVM* g_jvm = nullptr;
23static jclass native_library = nullptr; 29static jclass native_library = nullptr;
24 30
31#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
25#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) 32#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
26#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) 33#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
27#define F(JMethodID) static jmethodID JMethodID = nullptr; 34#define F(JMethodID) static jmethodID JMethodID = nullptr;
35ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
28ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) 36ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
29ANDROID_STORAGE_FUNCTIONS(FS) 37ANDROID_STORAGE_FUNCTIONS(FS)
30#undef F 38#undef F
31#undef FS 39#undef FS
32#undef FR 40#undef FR
41#undef FH
33 42
34enum class OpenMode { 43enum class OpenMode {
35 Read, 44 Read,
@@ -62,4 +71,10 @@ ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
62#undef F 71#undef F
63#undef FR 72#undef FR
64 73
74#define FH(FunctionName, JMethodID, Caller, JMethodName, Signature) F(FunctionName)
75#define F(FunctionName) std::string FunctionName(const std::string& filepath);
76ANDROID_SINGLE_PATH_HELPER_FUNCTIONS(FH)
77#undef F
78#undef FH
79
65} // namespace Common::FS::Android 80} // 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
401} 401}
402 402
403std::string_view GetParentPath(std::string_view path) { 403std::string_view GetParentPath(std::string_view path) {
404 if (path.empty()) {
405 return path;
406 }
407
408#ifdef ANDROID
409 if (path[0] != '/') {
410 std::string path_string{path};
411 return FS::Android::GetParentDirectory(path_string);
412 }
413#endif
404 const auto name_bck_index = path.rfind('\\'); 414 const auto name_bck_index = path.rfind('\\');
405 const auto name_fwd_index = path.rfind('/'); 415 const auto name_fwd_index = path.rfind('/');
406 std::size_t name_index; 416 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 @@
14#include <windows.h> 14#include <windows.h>
15#endif 15#endif
16 16
17#ifdef ANDROID
18#include <common/fs/fs_android.h>
19#endif
20
17namespace Common { 21namespace Common {
18 22
19/// Make a string lowercase 23/// Make a string lowercase
@@ -63,6 +67,14 @@ bool SplitPath(const std::string& full_path, std::string* _pPath, std::string* _
63 if (full_path.empty()) 67 if (full_path.empty())
64 return false; 68 return false;
65 69
70#ifdef ANDROID
71 if (full_path[0] != '/') {
72 *_pPath = Common::FS::Android::GetParentDirectory(full_path);
73 *_pFilename = Common::FS::Android::GetFilename(full_path);
74 return true;
75 }
76#endif
77
66 std::size_t dir_end = full_path.find_last_of("/" 78 std::size_t dir_end = full_path.find_last_of("/"
67// windows needs the : included for something like just "C:" to be considered a directory 79// windows needs the : included for something like just "C:" to be considered a directory
68#ifdef _WIN32 80#ifdef _WIN32