diff --git a/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java b/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java index 043f935cb..b18b282ed 100644 --- a/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java +++ b/src/android/app/src/main/java/org/citra/citra_android/NativeLibrary.java @@ -8,11 +8,13 @@ package org.citra.citra_android; import android.app.AlertDialog; import android.content.res.Configuration; -import android.preference.PreferenceManager; +import android.text.Html; +import android.text.method.LinkMovementMethod; import android.view.Surface; import android.view.ViewGroup; import android.widget.EditText; import android.widget.FrameLayout; +import android.widget.TextView; import org.citra.citra_android.activities.EmulationActivity; import org.citra.citra_android.utils.EmulationMenuSettings; @@ -444,6 +446,46 @@ public final class NativeLibrary { return alertPromptButton; } + public static void exitEmulationActivity(int resultCode) { + final int Success = 0; + final int ErrorNotInitialized = 1; + final int ErrorGetLoader = 2; + final int ErrorSystemMode = 3; + final int ErrorLoader = 4; + final int ErrorLoader_ErrorEncrypted = 5; + final int ErrorLoader_ErrorInvalidFormat = 6; + final int ErrorSystemFiles = 7; + final int ErrorVideoCore = 8; + final int ErrorVideoCore_ErrorGenericDrivers = 9; + final int ErrorVideoCore_ErrorBelowGL33 = 10; + final int ShutdownRequested = 11; + final int ErrorUnknown = 12; + + final EmulationActivity emulationActivity = sEmulationActivity.get(); + if (emulationActivity == null) { + Log.warning("[NativeLibrary] EmulationActivity is null, can't exit."); + return; + } + + int captionId = R.string.loader_error_invalid_format; + if (resultCode == ErrorLoader_ErrorEncrypted) { + captionId = R.string.loader_error_encrypted; + } + + AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity) + .setTitle(captionId) + .setMessage(Html.fromHtml("Please follow the guides to redump your game cartidges or installed titles.")) + .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> emulationActivity.exitWithAnimation()) + .setOnDismissListener(dialogInterface -> emulationActivity.exitWithAnimation()); + emulationActivity.runOnUiThread(() -> { + AlertDialog alert = builder.create(); + alert.show(); + ((TextView) alert.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); + }); + + return; + } + public static void setEmulationActivity(EmulationActivity emulationActivity) { Log.verbose("[NativeLibrary] Registering EmulationActivity."); sEmulationActivity = new WeakReference<>(emulationActivity); diff --git a/src/android/app/src/main/java/org/citra/citra_android/model/GameDatabase.java b/src/android/app/src/main/java/org/citra/citra_android/model/GameDatabase.java index b249c312e..2e32f87ea 100644 --- a/src/android/app/src/main/java/org/citra/citra_android/model/GameDatabase.java +++ b/src/android/app/src/main/java/org/citra/citra_android/model/GameDatabase.java @@ -140,7 +140,7 @@ public final class GameDatabase extends SQLiteOpenHelper { null); // Order of folders is irrelevant. Set allowedExtensions = new HashSet(Arrays.asList( - ".3ds", ".3dsx", ".elf", ".axf", ".cci", ".cxi", ".app")); + ".3ds", ".3dsx", ".elf", ".axf", ".cci", ".cxi", ".cia", ".app", ".rar", ".zip", ".7z", ".torrent", ".tar", ".gz")); // Possibly overly defensive, but ensures that moveToNext() does not skip a row. folderCursor.moveToPosition(-1); diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 78d04d339..36d48ca27 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -21,6 +21,7 @@ static jmethodID s_display_alert_prompt; static jmethodID s_alert_prompt_button; static jmethodID s_is_portrait_mode; static jmethodID s_landscape_screen_layout; +static jmethodID s_exit_emulation_activity; namespace IDCache { @@ -67,6 +68,10 @@ jmethodID GetLandscapeScreenLayout() { return s_landscape_screen_layout; } +jmethodID GetExitEmulationActivity() { + return s_exit_emulation_activity; +} + } // namespace IDCache #ifdef __cplusplus @@ -103,6 +108,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { s_is_portrait_mode = env->GetStaticMethodID(s_native_library_class, "isPortraitMode", "()Z"); s_landscape_screen_layout = env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I"); + s_exit_emulation_activity = + env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); return JNI_VERSION; } diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index 9d6bba545..ac9f24d97 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -15,5 +15,6 @@ jmethodID GetDisplayAlertPrompt(); jmethodID GetAlertPromptButton(); jmethodID GetIsPortraitMode(); jmethodID GetLandscapeScreenLayout(); +jmethodID GetExitEmulationActivity(); } // namespace IDCache diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index b83306753..18b568d25 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -109,7 +109,7 @@ public: void ShowError(const std::string& error) override {} }; -static int RunCitra(const std::string& filepath) { +static Core::System::ResultStatus RunCitra(const std::string& filepath) { // Citra core only supports a single running instance std::lock_guard lock(running_mutex); @@ -120,7 +120,7 @@ static int RunCitra(const std::string& filepath) { if (filepath.empty()) { LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified"); - return -1; + return Core::System::ResultStatus::ErrorLoader; } Core::System& system{Core::System::GetInstance()}; @@ -143,33 +143,8 @@ static int RunCitra(const std::string& filepath) { SCOPE_EXIT({ window.reset(); }); const Core::System::ResultStatus load_result{system.Load(*window, filepath)}; - switch (load_result) { - case Core::System::ResultStatus::ErrorGetLoader: - LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath); - return -1; - case Core::System::ResultStatus::ErrorLoader: - LOG_CRITICAL(Frontend, "Failed to load ROM!"); - return -1; - case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: - LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " - "being used with Citra. \n\n For more information on dumping and " - "decrypting games, please refer to: " - "https://citra-emu.org/wiki/dumping-game-cartridges/"); - return -1; - case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat: - LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported."); - return -1; - case Core::System::ResultStatus::ErrorNotInitialized: - LOG_CRITICAL(Frontend, "Core not initialized"); - return -1; - case Core::System::ResultStatus::ErrorSystemMode: - LOG_CRITICAL(Frontend, "Failed to determine system mode!"); - return -1; - case Core::System::ResultStatus::ErrorVideoCore: - LOG_CRITICAL(Frontend, "VideoCore not initialized"); - return -1; - case Core::System::ResultStatus::Success: - break; // Expected case + if (load_result != Core::System::ResultStatus::Success) { + return load_result; } auto& telemetry_session = Core::System::GetInstance().TelemetrySession(); @@ -192,7 +167,7 @@ static int RunCitra(const std::string& filepath) { } } - return {}; + return Core::System::ResultStatus::Success; } void Java_org_citra_citra_1android_NativeLibrary_SurfaceChanged(JNIEnv* env, jobject obj, @@ -483,5 +458,10 @@ void Java_org_citra_citra_1android_NativeLibrary_Run__Ljava_lang_String_2(JNIEnv is_running = false; running_cv.notify_all(); } - RunCitra(path); + + const Core::System::ResultStatus result{RunCitra(path)}; + if (result != Core::System::ResultStatus::Success) { + env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(), + IDCache::GetExitEmulationActivity(), static_cast(result)); + } } diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 08eb3f1a1..9934e7048 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -103,6 +103,10 @@ Graphics Audio + + Your ROM is encrypted + Invalid ROM format + Take Screenshot Exit diff --git a/src/core/core.cpp b/src/core/core.cpp index d6a284810..464d0f950 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -518,15 +518,17 @@ void System::RegisterImageInterface(std::shared_ptr im void System::Shutdown(bool is_deserializing) { // Log last frame performance stats - const auto perf_results = GetAndResetPerfStats(); - telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed", - perf_results.emulation_speed * 100.0); - telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate", - perf_results.game_fps); - telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", - perf_results.frametime * 1000.0); - telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", - perf_stats->GetMeanFrametime()); + if (telemetry_session) { + const auto perf_results = GetAndResetPerfStats(); + telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed", + perf_results.emulation_speed * 100.0); + telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate", + perf_results.game_fps); + telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", + perf_results.frametime * 1000.0); + telemetry_session->AddField(Telemetry::FieldType::Performance, "Mean_Frametime_MS", + perf_stats->GetMeanFrametime()); + } // Shutdown emulation session VideoCore::Shutdown(); @@ -546,7 +548,7 @@ void System::Shutdown(bool is_deserializing) { cpu_cores.clear(); timing.reset(); - if (video_dumper->IsDumping()) { + if (video_dumper && video_dumper->IsDumping()) { video_dumper->StopDumping(); }