From 2b815d046467008b2e5114dfe6367f3a1820b0d4 Mon Sep 17 00:00:00 2001 From: BreadFish64 Date: Tue, 28 Apr 2020 12:53:57 -0500 Subject: [PATCH] android/GameList: Scan for installed titles --- .../org/citra/citra_emu/NativeLibrary.java | 2 + .../citra/citra_emu/model/GameDatabase.java | 87 ++++++++++--------- src/android/app/src/main/jni/native.cpp | 37 ++++++++ src/android/app/src/main/jni/native.h | 3 + src/common/file_util.cpp | 4 +- 5 files changed, 91 insertions(+), 42 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java index c4a745df3..e93306a69 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.java @@ -146,6 +146,8 @@ public final class NativeLibrary { */ public static native void SetUserDirectory(String directory); + public static native String[] GetInstalledGamePaths(); + // Create the config.ini file. public static native void CreateConfigFile(); diff --git a/src/android/app/src/main/java/org/citra/citra_emu/model/GameDatabase.java b/src/android/app/src/main/java/org/citra/citra_emu/model/GameDatabase.java index 32c283d99..4a1c9660c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/model/GameDatabase.java +++ b/src/android/app/src/main/java/org/citra/citra_emu/model/GameDatabase.java @@ -13,6 +13,7 @@ import java.io.File; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.function.Consumer; import rx.Observable; @@ -147,6 +148,49 @@ public final class GameDatabase extends SQLiteOpenHelper { // Possibly overly defensive, but ensures that moveToNext() does not skip a row. folderCursor.moveToPosition(-1); + Consumer AttemptToAddGame = filePath -> { + String name = NativeLibrary.GetTitle(filePath); + + // If the game's title field is empty, use the filename. + if (name.isEmpty()) { + name = filePath.substring(filePath.lastIndexOf("/") + 1); + } + + String gameId = NativeLibrary.GetGameId(filePath); + + // If the game's ID field is empty, use the filename without extension. + if (gameId.isEmpty()) { + gameId = filePath.substring(filePath.lastIndexOf("/") + 1, + filePath.lastIndexOf(".")); + } + + ContentValues game = Game.asContentValues(name, + NativeLibrary.GetDescription(filePath).replace("\n", " "), + NativeLibrary.GetRegions(filePath), + filePath, + gameId, + NativeLibrary.GetCompany(filePath)); + + // Try to update an existing game first. + int rowsMatched = database.update(TABLE_NAME_GAMES, // Which table to update. + game, + // The values to fill the row with. + KEY_GAME_ID + " = ?", + // The WHERE clause used to find the right row. + new String[]{game.getAsString( + KEY_GAME_ID)}); // The ? in WHERE clause is replaced with this, + // which is provided as an array because there + // could potentially be more than one argument. + + // If update fails, insert a new game instead. + if (rowsMatched == 0) { + Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE)); + database.insert(TABLE_NAME_GAMES, null, game); + } else { + Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE)); + } + }; + // Iterate through all results of the DB query (i.e. all folders in the library.) while (folderCursor.moveToNext()) { String folderPath = folderCursor.getString(FOLDER_COLUMN_PATH); @@ -168,46 +212,7 @@ public final class GameDatabase extends SQLiteOpenHelper { // Check that the file has an extension we care about before trying to read out of it. if (allowedExtensions.contains(fileExtension.toLowerCase())) { - String name = NativeLibrary.GetTitle(filePath); - - // If the game's title field is empty, use the filename. - if (name.isEmpty()) { - name = filePath.substring(filePath.lastIndexOf("/") + 1); - } - - String gameId = NativeLibrary.GetGameId(filePath); - - // If the game's ID field is empty, use the filename without extension. - if (gameId.isEmpty()) { - gameId = filePath.substring(filePath.lastIndexOf("/") + 1, - filePath.lastIndexOf(".")); - } - - ContentValues game = Game.asContentValues(name, - NativeLibrary.GetDescription(filePath).replace("\n", " "), - NativeLibrary.GetRegions(filePath), - filePath, - gameId, - NativeLibrary.GetCompany(filePath)); - - // Try to update an existing game first. - int rowsMatched = database.update(TABLE_NAME_GAMES, // Which table to update. - game, - // The values to fill the row with. - KEY_GAME_ID + " = ?", - // The WHERE clause used to find the right row. - new String[]{game.getAsString( - KEY_GAME_ID)}); // The ? in WHERE clause is replaced with this, - // which is provided as an array because there - // could potentially be more than one argument. - - // If update fails, insert a new game instead. - if (rowsMatched == 0) { - Log.verbose("[GameDatabase] Adding game: " + game.getAsString(KEY_GAME_TITLE)); - database.insert(TABLE_NAME_GAMES, null, game); - } else { - Log.verbose("[GameDatabase] Updated game: " + game.getAsString(KEY_GAME_TITLE)); - } + AttemptToAddGame.accept(filePath); } } } @@ -227,6 +232,8 @@ public final class GameDatabase extends SQLiteOpenHelper { fileCursor.close(); folderCursor.close(); + + Arrays.stream(NativeLibrary.GetInstalledGamePaths()).forEach(AttemptToAddGame); database.close(); } diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 26db411d6..38ad9aac6 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -221,6 +221,43 @@ void Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory(JNIEnv* env, FileUtil::SetCurrentDir(GetJString(env, j_directory)); } +jobjectArray Java_org_citra_citra_1emu_NativeLibrary_GetInstalledGamePaths( + JNIEnv* env, [[maybe_unused]] jclass clazz) { + std::vector games; + const FileUtil::DirectoryEntryCallable ScanDir = + [&games, &ScanDir](u64*, const std::string& directory, const std::string& virtual_name) { + std::string path = directory + virtual_name; + if (FileUtil::IsDirectory(path)) { + path += '/'; + FileUtil::ForeachDirectoryEntry(nullptr, path, ScanDir); + } else { + auto loader = Loader::GetLoader(path); + if (loader) { + bool executable{}; + const Loader::ResultStatus result = loader->IsExecutable(executable); + if (Loader::ResultStatus::Success == result && executable) { + games.emplace_back(path); + } + } + } + return true; + }; + ScanDir(nullptr, "", + FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + + "Nintendo " + "3DS/00000000000000000000000000000000/" + "00000000000000000000000000000000/title/00040000"); + ScanDir(nullptr, "", + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "00000000000000000000000000000000/title/00040010"); + jobjectArray jgames = + env->NewObjectArray(static_cast(games.size()), env->FindClass("java/lang/String"), + nullptr); + for (jsize i = 0; i < games.size(); ++i) + env->SetObjectArrayElement(jgames, i, env->NewStringUTF(games[i].c_str())); + return jgames; +} + void Java_org_citra_citra_1emu_NativeLibrary_UnPauseEmulation(JNIEnv* env, [[maybe_unused]] jclass clazz) { pause_emulation = false; diff --git a/src/android/app/src/main/jni/native.h b/src/android/app/src/main/jni/native.h index 0621d2eb0..a7b5a9514 100644 --- a/src/android/app/src/main/jni/native.h +++ b/src/android/app/src/main/jni/native.h @@ -70,6 +70,9 @@ JNIEXPORT jstring JNICALL Java_org_citra_citra_1emu_NativeLibrary_GetGitRevision JNIEXPORT void JNICALL Java_org_citra_citra_1emu_NativeLibrary_SetUserDirectory( JNIEnv* env, jclass clazz, jstring j_directory); +JNIEXPORT jobjectArray JNICALL +Java_org_citra_citra_1emu_NativeLibrary_GetInstalledGamePaths(JNIEnv* env, jclass clazz); + JNIEXPORT void JNICALL Java_org_citra_citra_1emu_utils_DirectoryInitialization_SetSysDirectory( JNIEnv* env, jclass clazz, jstring path_); diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 7418d0610..aa4e0a143 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -695,8 +695,8 @@ void SetUserPath(const std::string& path) { g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); #elif ANDROID - if (FileUtil::Exists(ROOT_DIR DIR_SEP SDCARD_DIR)) { - user_path = ROOT_DIR DIR_SEP SDCARD_DIR DIR_SEP EMU_DATA_DIR DIR_SEP; + if (FileUtil::Exists(DIR_SEP SDCARD_DIR)) { + user_path = DIR_SEP SDCARD_DIR DIR_SEP EMU_DATA_DIR DIR_SEP; g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); }