From ba7f896518009853b622045a1e47a9a309a20077 Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Fri, 14 Aug 2020 23:25:26 +0800 Subject: [PATCH] android: Handle core errors The errors are handled in a similar manner to the Qt frontend: an AlertDialog will pop up, prompting the user to select 'Abort' or 'Continue'. Error messages are translatable as string values. --- .../org/citra/citra_emu/NativeLibrary.java | 78 +++++++++++++++++++ src/android/app/src/main/jni/id_cache.cpp | 16 ++++ src/android/app/src/main/jni/id_cache.h | 2 + src/android/app/src/main/jni/native.cpp | 38 ++++++++- .../app/src/main/res/values/strings.xml | 10 +++ 5 files changed, 143 insertions(+), 1 deletion(-) 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 62685eb9f..01e5aca95 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 @@ -211,6 +211,84 @@ public final class NativeLibrary { */ public static native void SwapScreens(boolean swap_screens, int rotation); + public enum CoreError { + ErrorSystemFiles, + ErrorSavestate, + ErrorUnknown, + } + + private static boolean coreErrorAlertResult = false; + + /** + * Handles a core error. + * @return true: continue; false: abort + */ + public static boolean OnCoreError(CoreError error, String details) { + final EmulationActivity emulationActivity = sEmulationActivity.get(); + if (emulationActivity == null) { + Log.error("[NativeLibrary] EmulationActivity not present"); + return false; + } + + String title, message; + switch (error) { + case ErrorSystemFiles: { + title = emulationActivity.getString(R.string.system_archive_not_found); + message = emulationActivity.getString(R.string.system_archive_not_found_message, details.isEmpty() ? emulationActivity.getString(R.string.system_archive_general) : details); + break; + } + case ErrorSavestate: { + title = emulationActivity.getString(R.string.save_load_error); + message = details; + break; + } + case ErrorUnknown: { + title = emulationActivity.getString(R.string.fatal_error); + message = emulationActivity.getString(R.string.fatal_error_message); + break; + } + default: { + return true; + } + } + + // Create object used for waiting. + final Object lock = new Object(); + final AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity) + .setTitle(title) + .setMessage(message) + .setPositiveButton(R.string.continue_button, (dialog, which) -> { + coreErrorAlertResult = true; + synchronized (lock) { + lock.notify(); + } + }) + .setNegativeButton(R.string.abort_button, (dialog, which) -> { + coreErrorAlertResult = false; + synchronized (lock) { + lock.notify(); + } + }).setOnDismissListener(dialog -> { + coreErrorAlertResult = true; + synchronized (lock) { + lock.notify(); + } + }); + + // Show the AlertDialog on the main thread. + emulationActivity.runOnUiThread(builder::show); + + // Wait for the lock to notify that it is complete. + synchronized (lock) { + try { + lock.wait(); + } catch (Exception ignored) { + } + } + + return coreErrorAlertResult; + } + public static boolean isPortraitMode() { return CitraApplication.getAppContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index b51b11846..d716640b6 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -19,7 +19,9 @@ static constexpr jint JNI_VERSION = JNI_VERSION_1_6; static JavaVM* s_java_vm; static jclass s_native_library_class; +static jclass s_core_error_class; static jclass s_savestate_info_class; +static jmethodID s_on_core_error; static jmethodID s_display_alert_msg; static jmethodID s_display_alert_prompt; static jmethodID s_alert_prompt_button; @@ -54,10 +56,18 @@ jclass GetNativeLibraryClass() { return s_native_library_class; } +jclass GetCoreErrorClass() { + return s_core_error_class; +} + jclass GetSavestateInfoClass() { return s_savestate_info_class; } +jmethodID GetOnCoreError() { + return s_on_core_error; +} + jmethodID GetDisplayAlertMsg() { return s_display_alert_msg; } @@ -118,6 +128,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); s_savestate_info_class = reinterpret_cast( env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo"))); + s_core_error_class = reinterpret_cast( + env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$CoreError"))); + s_on_core_error = env->GetStaticMethodID( + s_native_library_class, "OnCoreError", + "(Lorg/citra/citra_emu/NativeLibrary$CoreError;Ljava/lang/String;)Z"); s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg", "(Ljava/lang/String;Ljava/lang/String;Z)Z"); s_display_alert_prompt = @@ -150,6 +165,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { env->DeleteGlobalRef(s_native_library_class); env->DeleteGlobalRef(s_savestate_info_class); + env->DeleteGlobalRef(s_core_error_class); MiiSelector::CleanupJNI(env); SoftwareKeyboard::CleanupJNI(env); Camera::StillImage::CleanupJNI(env); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index ee3fe6be8..fc4987b46 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -12,7 +12,9 @@ namespace IDCache { JNIEnv* GetEnvForThread(); jclass GetNativeLibraryClass(); +jclass GetCoreErrorClass(); jclass GetSavestateInfoClass(); +jmethodID GetOnCoreError(); jmethodID GetDisplayAlertMsg(); jmethodID GetDisplayAlertPrompt(); jmethodID GetAlertPromptButton(); diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 7a6f89e90..62b57db10 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -96,6 +96,29 @@ static int AlertPromptButton() { IDCache::GetAlertPromptButton())); } +static jobject ToJavaCoreError(Core::System::ResultStatus result) { + static const std::map CoreErrorNameMap{ + {Core::System::ResultStatus::ErrorSystemFiles, "ErrorSystemFiles"}, + {Core::System::ResultStatus::ErrorSavestate, "ErrorSavestate"}, + {Core::System::ResultStatus::ErrorUnknown, "ErrorUnknown"}, + }; + + const auto name = CoreErrorNameMap.count(result) ? CoreErrorNameMap.at(result) : "ErrorUnknown"; + + JNIEnv* env = IDCache::GetEnvForThread(); + const jclass core_error_class = IDCache::GetCoreErrorClass(); + return env->GetStaticObjectField( + core_error_class, env->GetStaticFieldID(core_error_class, name, + "Lorg/citra/citra_emu/NativeLibrary$CoreError;")); +} + +static bool HandleCoreError(Core::System::ResultStatus result, const std::string& details) { + JNIEnv* env = IDCache::GetEnvForThread(); + return env->CallStaticBooleanMethod(IDCache::GetNativeLibraryClass(), IDCache::GetOnCoreError(), + ToJavaCoreError(result), + env->NewStringUTF(details.c_str())) != JNI_FALSE; +} + static Camera::NDK::Factory* g_ndk_factory{}; static void TryShutdown() { @@ -187,7 +210,20 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { // Start running emulation while (is_running) { if (!pause_emulation) { - system.RunLoop(); + const auto result = system.RunLoop(); + if (result == Core::System::ResultStatus::Success) { + continue; + } + if (result == Core::System::ResultStatus::ShutdownRequested) { + return result; // This also exits the emulation activity + } else { + InputManager::NDKMotionHandler()->DisableSensors(); + if (!HandleCoreError(result, system.GetStatusDetails())) { + // Frontend requests us to abort + return result; + } + InputManager::NDKMotionHandler()->EnableSensors(); + } } else { // Ensure no audio bleeds out while game is paused const float volume = Settings::values.volume; diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index afe96d16e..a661203d5 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -201,4 +201,14 @@ Microphone Citra needs to access your microphone to emulate the 3DS\'s microphone.\n\nAlternatively, you can also change \"Audio Input Device\" in Audio Settings. + + + Abort + Continue + System Archive Not Found + %s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs. + A system archive + Save/Load Error + Fatal Error + A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.