From a9e29a3972dc0d74a6bd42cb767e5ace86318937 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Tue, 24 Oct 2023 17:02:13 -0400 Subject: [PATCH] android: Refactor game metadata collection to new file This also removes irrelevant data and adds new information from/to the Game data class and RomMetadata struct --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 31 ----- .../org/yuzu/yuzu_emu/adapters/GameAdapter.kt | 2 +- .../main/java/org/yuzu/yuzu_emu/model/Game.kt | 17 ++- .../org/yuzu/yuzu_emu/model/GamesViewModel.kt | 9 +- .../org/yuzu/yuzu_emu/utils/GameHelper.kt | 17 ++- .../org/yuzu/yuzu_emu/utils/GameIconUtils.kt | 3 +- .../org/yuzu/yuzu_emu/utils/GameMetadata.kt | 20 ++++ src/android/app/src/main/jni/CMakeLists.txt | 1 + .../app/src/main/jni/game_metadata.cpp | 112 ++++++++++++++++++ src/android/app/src/main/jni/native.cpp | 44 ------- 10 files changed, 154 insertions(+), 102 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameMetadata.kt create mode 100644 src/android/app/src/main/jni/game_metadata.cpp 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 { external fun initGameIni(gameID: String?) - /** - * Gets the embedded icon within the given ROM. - * - * @param filename the file path to the ROM. - * @return a byte array containing the JPEG data for the icon. - */ - external fun getIcon(filename: String): ByteArray - - /** - * Gets the embedded title of the given ISO/ROM. - * - * @param filename The file path to the ISO/ROM. - * @return the embedded title of the ISO/ROM. - */ - external fun getTitle(filename: String): String - - external fun getDescription(filename: String): String - - external fun getGameId(filename: String): String - - external fun getRegions(filename: String): String - - external fun getCompany(filename: String): String - - external fun isHomebrew(filename: String): Boolean - external fun setAppDirectory(directory: String) /** @@ -293,11 +267,6 @@ object NativeLibrary { */ external fun stopEmulation() - /** - * Resets the in-memory ROM metadata cache. - */ - external fun resetRomMetadata() - /** * Returns true if emulation is running (or is paused). */ 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) : private class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: Game, newItem: Game): Boolean { - return oldItem.gameId == newItem.gameId + return oldItem.programId == newItem.programId } 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 @Serializable class Game( val title: String, - val description: String, - val regions: String, val path: String, - val gameId: String, - val company: String, + val programId: String, + val developer: String, + val version: String, val isHomebrew: Boolean ) : Parcelable { - val keyAddedToLibraryTime get() = "${gameId}_AddedToLibraryTime" - val keyLastPlayedTime get() = "${gameId}_LastPlayed" + val keyAddedToLibraryTime get() = "${programId}_AddedToLibraryTime" + val keyLastPlayedTime get() = "${programId}_LastPlayed" override fun equals(other: Any?): Boolean { if (other !is Game) { @@ -32,11 +31,9 @@ class Game( override fun hashCode(): Int { var result = title.hashCode() - result = 31 * result + description.hashCode() - result = 31 * result + regions.hashCode() result = 31 * result + path.hashCode() - result = 31 * result + gameId.hashCode() - result = 31 * result + company.hashCode() + result = 31 * result + programId.hashCode() + result = 31 * result + developer.hashCode() result = 31 * result + isHomebrew.hashCode() return result } 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 import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.MissingFieldException import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.utils.GameHelper +import org.yuzu.yuzu_emu.utils.GameMetadata -@OptIn(ExperimentalSerializationApi::class) class GamesViewModel : ViewModel() { val games: StateFlow> get() = _games private val _games = MutableStateFlow(emptyList()) @@ -58,7 +56,8 @@ class GamesViewModel : ViewModel() { val game: Game try { game = Json.decodeFromString(it) - } catch (e: MissingFieldException) { + } catch (e: Exception) { + // We don't care about any errors related to parsing the game cache return@forEach } @@ -113,7 +112,7 @@ class GamesViewModel : ViewModel() { viewModelScope.launch { withContext(Dispatchers.IO) { - NativeLibrary.resetRomMetadata() + GameMetadata.resetMetadata() setGames(GameHelper.getGames()) _isReloading.value = false 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 { fun getGame(uri: Uri, addedToLibrary: Boolean): Game { val filePath = uri.toString() - var name = NativeLibrary.getTitle(filePath) + var name = GameMetadata.getTitle(filePath) // If the game's title field is empty, use the filename. if (name.isEmpty()) { name = FileUtil.getFilename(uri) } - var gameId = NativeLibrary.getGameId(filePath) + var programId = GameMetadata.getProgramId(filePath) // If the game's ID field is empty, use the filename without extension. - if (gameId.isEmpty()) { - gameId = name.substring(0, name.lastIndexOf(".")) + if (programId.isEmpty()) { + programId = name.substring(0, name.lastIndexOf(".")) } val newGame = Game( name, - NativeLibrary.getDescription(filePath).replace("\n", " "), - NativeLibrary.getRegions(filePath), filePath, - gameId, - NativeLibrary.getCompany(filePath), - NativeLibrary.isHomebrew(filePath) + programId, + GameMetadata.getDeveloper(filePath), + GameMetadata.getVersion(filePath), + GameMetadata.getIsHomebrew(filePath) ) 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 import coil.memory.MemoryCache import coil.request.ImageRequest import coil.request.Options -import org.yuzu.yuzu_emu.NativeLibrary import org.yuzu.yuzu_emu.R import org.yuzu.yuzu_emu.YuzuApplication import org.yuzu.yuzu_emu.model.Game @@ -36,7 +35,7 @@ class GameIconFetcher( } private fun decodeGameIcon(uri: String): Bitmap? { - val data = NativeLibrary.getIcon(uri) + val data = GameMetadata.getIcon(uri) return BitmapFactory.decodeByteArray( data, 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 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.utils + +object GameMetadata { + external fun getTitle(path: String): String + + external fun getProgramId(path: String): String + + external fun getDeveloper(path: String): String + + external fun getVersion(path: String): String + + external fun getIcon(path: String): ByteArray + + external fun getIsHomebrew(path: String): Boolean + + external fun resetMetadata() +} 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 native.h native_config.cpp uisettings.cpp + game_metadata.cpp ) set_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 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include "core/loader/loader.h" +#include "jni/android_common/android_common.h" +#include "native.h" + +struct RomMetadata { + std::string title; + u64 programId; + std::string developer; + std::string version; + std::vector icon; + bool isHomebrew; +}; + +std::unordered_map m_rom_metadata_cache; + +RomMetadata CacheRomMetadata(const std::string& path) { + const auto file = + Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path); + auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0); + + RomMetadata entry; + loader->ReadTitle(entry.title); + loader->ReadProgramId(entry.programId); + loader->ReadIcon(entry.icon); + + const FileSys::PatchManager pm{ + entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(), + EmulationSession::GetInstance().System().GetContentProvider()}; + const auto control = pm.GetControlMetadata(); + + if (control.first != nullptr) { + entry.developer = control.first->GetDeveloperName(); + entry.version = control.first->GetVersionString(); + } else { + FileSys::NACP nacp; + if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) { + entry.developer = nacp.GetDeveloperName(); + } else { + entry.developer = ""; + } + + entry.version = "1.0.0"; + } + + if (loader->GetFileType() == Loader::FileType::NRO) { + auto loader_nro = reinterpret_cast(loader.get()); + entry.isHomebrew = loader_nro->IsHomebrew(); + } else { + entry.isHomebrew = false; + } + + m_rom_metadata_cache[path] = entry; + + return entry; +} + +RomMetadata GetRomMetadata(const std::string& path) { + if (auto search = m_rom_metadata_cache.find(path); search != m_rom_metadata_cache.end()) { + return search->second; + } + + return CacheRomMetadata(path); +} + +extern "C" { + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getTitle(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, GetRomMetadata(GetJString(env, jpath)).title); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getProgramId(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, std::to_string(GetRomMetadata(GetJString(env, jpath)).programId)); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getDeveloper(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, GetRomMetadata(GetJString(env, jpath)).developer); +} + +jstring Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getVersion(JNIEnv* env, jobject obj, + jstring jpath) { + return ToJString(env, GetRomMetadata(GetJString(env, jpath)).version); +} + +jbyteArray Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIcon(JNIEnv* env, jobject obj, + jstring jpath) { + auto icon_data = GetRomMetadata(GetJString(env, jpath)).icon; + jbyteArray icon = env->NewByteArray(static_cast(icon_data.size())); + env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), + reinterpret_cast(icon_data.data())); + return icon; +} + +jboolean Java_org_yuzu_yuzu_1emu_utils_GameMetadata_getIsHomebrew(JNIEnv* env, jobject obj, + jstring jpath) { + return static_cast(GetRomMetadata(GetJString(env, jpath)).isHomebrew); +} + +void Java_org_yuzu_yuzu_1emu_utils_GameMetadata_resetMetadata(JNIEnv* env, jobject obj) { + return m_rom_metadata_cache.clear(); +} + +} // 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 EmulationSession::GetInstance().HaltEmulation(); } -void Java_org_yuzu_yuzu_1emu_NativeLibrary_resetRomMetadata(JNIEnv* env, jclass clazz) { - EmulationSession::GetInstance().ResetRomMetadata(); -} - jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isRunning(JNIEnv* env, jclass clazz) { return static_cast(EmulationSession::GetInstance().IsRunning()); } @@ -667,46 +663,6 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_onTouchReleased(JNIEnv* env, jclass c } } -jbyteArray Java_org_yuzu_yuzu_1emu_NativeLibrary_getIcon(JNIEnv* env, jclass clazz, - jstring j_filename) { - jauto icon_data = EmulationSession::GetInstance().GetRomIcon(GetJString(env, j_filename)); - jbyteArray icon = env->NewByteArray(static_cast(icon_data.size())); - env->SetByteArrayRegion(icon, 0, env->GetArrayLength(icon), - reinterpret_cast(icon_data.data())); - return icon; -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getTitle(JNIEnv* env, jclass clazz, - jstring j_filename) { - jauto title = EmulationSession::GetInstance().GetRomTitle(GetJString(env, j_filename)); - return env->NewStringUTF(title.c_str()); -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getDescription(JNIEnv* env, jclass clazz, - jstring j_filename) { - return j_filename; -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getGameId(JNIEnv* env, jclass clazz, - jstring j_filename) { - return j_filename; -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getRegions(JNIEnv* env, jclass clazz, - jstring j_filename) { - return env->NewStringUTF(""); -} - -jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getCompany(JNIEnv* env, jclass clazz, - jstring j_filename) { - return env->NewStringUTF(""); -} - -jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHomebrew(JNIEnv* env, jclass clazz, - jstring j_filename) { - return EmulationSession::GetInstance().GetIsHomebrew(GetJString(env, j_filename)); -} - void Java_org_yuzu_yuzu_1emu_NativeLibrary_initializeEmulation(JNIEnv* env, jclass clazz) { // Create the default config.ini. Config{};