summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt31
-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/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.txt1
-rw-r--r--src/android/app/src/main/jni/game_metadata.cpp112
-rw-r--r--src/android/app/src/main/jni/native.cpp44
10 files changed, 154 insertions, 102 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..22c9b05de 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
@@ -215,32 +215,6 @@ object NativeLibrary {
215 215
216 external fun initGameIni(gameID: String?) 216 external fun initGameIni(gameID: String?)
217 217
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) 218 external fun setAppDirectory(directory: String)
245 219
246 /** 220 /**
@@ -294,11 +268,6 @@ object NativeLibrary {
294 external fun stopEmulation() 268 external fun stopEmulation()
295 269
296 /** 270 /**
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). 271 * Returns true if emulation is running (or is paused).
303 */ 272 */
304 external fun isRunning(): Boolean 273 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/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 7193903da..1c36661f5 100644
--- a/src/android/app/src/main/jni/CMakeLists.txt
+++ b/src/android/app/src/main/jni/CMakeLists.txt
@@ -17,6 +17,7 @@ add_library(yuzu-android SHARED
17 native.h 17 native.h
18 native_config.cpp 18 native_config.cpp
19 uisettings.cpp 19 uisettings.cpp
20 game_metadata.cpp
20) 21)
21 22
22set_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 629be3d81..686b73588 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -558,10 +558,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_stopEmulation(JNIEnv* env, jclass cla
558 EmulationSession::GetInstance().HaltEmulation(); 558 EmulationSession::GetInstance().HaltEmulation();
559} 559}
560 560
561void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) {
562 EmulationSession::GetInstance().ResetRomMetadata();
563}
564
565jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) { 561jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) {
566 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning()); 562 return static_cast<jboolean>(EmulationSession::GetInstance().IsRunning());
567} 563}
@@ -667,46 +663,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c
667 } 663 }
668} 664}
669 665
670jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz,
671 jstring j_filename) {
672 jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename));
673 jbyteArray icon = env->NewByteArray(static_cast<jsize>(icon_data.size()));
674 env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon),
675 reinterpret_cast<jbyte*>(icon_data.data()));
676 return icon;
677}
678
679jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz,
680 jstring j_filename) {
681 jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename));
682 return env->NewStringUTF(title.c_str());
683}
684
685jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz,
686 jstring j_filename) {
687 return j_filename;
688}
689
690jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz,
691 jstring j_filename) {
692 return j_filename;
693}
694
695jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz,
696 jstring j_filename) {
697 return env->NewStringUTF("");
698}
699
700jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz,
701 jstring j_filename) {
702 return env->NewStringUTF("");
703}
704
705jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz,
706 jstring j_filename) {
707 return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename));
708}
709
710void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) { 666void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) {
711 // Create the default config.ini. 667 // Create the default config.ini.
712 Config{}; 668 Config{};