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.
This commit is contained in:
parent
cf84342e35
commit
c0942a59c8
@ -211,6 +211,84 @@ public final class NativeLibrary {
|
|||||||
*/
|
*/
|
||||||
public static native void SwapScreens(boolean swap_screens, int rotation);
|
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() {
|
public static boolean isPortraitMode() {
|
||||||
return CitraApplication.getAppContext().getResources().getConfiguration().orientation ==
|
return CitraApplication.getAppContext().getResources().getConfiguration().orientation ==
|
||||||
Configuration.ORIENTATION_PORTRAIT;
|
Configuration.ORIENTATION_PORTRAIT;
|
||||||
|
@ -19,7 +19,9 @@ static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
|||||||
static JavaVM* s_java_vm;
|
static JavaVM* s_java_vm;
|
||||||
|
|
||||||
static jclass s_native_library_class;
|
static jclass s_native_library_class;
|
||||||
|
static jclass s_core_error_class;
|
||||||
static jclass s_savestate_info_class;
|
static jclass s_savestate_info_class;
|
||||||
|
static jmethodID s_on_core_error;
|
||||||
static jmethodID s_display_alert_msg;
|
static jmethodID s_display_alert_msg;
|
||||||
static jmethodID s_display_alert_prompt;
|
static jmethodID s_display_alert_prompt;
|
||||||
static jmethodID s_alert_prompt_button;
|
static jmethodID s_alert_prompt_button;
|
||||||
@ -54,10 +56,18 @@ jclass GetNativeLibraryClass() {
|
|||||||
return s_native_library_class;
|
return s_native_library_class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jclass GetCoreErrorClass() {
|
||||||
|
return s_core_error_class;
|
||||||
|
}
|
||||||
|
|
||||||
jclass GetSavestateInfoClass() {
|
jclass GetSavestateInfoClass() {
|
||||||
return s_savestate_info_class;
|
return s_savestate_info_class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jmethodID GetOnCoreError() {
|
||||||
|
return s_on_core_error;
|
||||||
|
}
|
||||||
|
|
||||||
jmethodID GetDisplayAlertMsg() {
|
jmethodID GetDisplayAlertMsg() {
|
||||||
return s_display_alert_msg;
|
return s_display_alert_msg;
|
||||||
}
|
}
|
||||||
@ -118,6 +128,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
|
|||||||
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
|
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
|
||||||
s_savestate_info_class = reinterpret_cast<jclass>(
|
s_savestate_info_class = reinterpret_cast<jclass>(
|
||||||
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo")));
|
env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo")));
|
||||||
|
s_core_error_class = reinterpret_cast<jclass>(
|
||||||
|
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",
|
s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg",
|
||||||
"(Ljava/lang/String;Ljava/lang/String;Z)Z");
|
"(Ljava/lang/String;Ljava/lang/String;Z)Z");
|
||||||
s_display_alert_prompt =
|
s_display_alert_prompt =
|
||||||
@ -150,6 +165,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) {
|
|||||||
|
|
||||||
env->DeleteGlobalRef(s_native_library_class);
|
env->DeleteGlobalRef(s_native_library_class);
|
||||||
env->DeleteGlobalRef(s_savestate_info_class);
|
env->DeleteGlobalRef(s_savestate_info_class);
|
||||||
|
env->DeleteGlobalRef(s_core_error_class);
|
||||||
MiiSelector::CleanupJNI(env);
|
MiiSelector::CleanupJNI(env);
|
||||||
SoftwareKeyboard::CleanupJNI(env);
|
SoftwareKeyboard::CleanupJNI(env);
|
||||||
Camera::StillImage::CleanupJNI(env);
|
Camera::StillImage::CleanupJNI(env);
|
||||||
|
@ -12,7 +12,9 @@ namespace IDCache {
|
|||||||
|
|
||||||
JNIEnv* GetEnvForThread();
|
JNIEnv* GetEnvForThread();
|
||||||
jclass GetNativeLibraryClass();
|
jclass GetNativeLibraryClass();
|
||||||
|
jclass GetCoreErrorClass();
|
||||||
jclass GetSavestateInfoClass();
|
jclass GetSavestateInfoClass();
|
||||||
|
jmethodID GetOnCoreError();
|
||||||
jmethodID GetDisplayAlertMsg();
|
jmethodID GetDisplayAlertMsg();
|
||||||
jmethodID GetDisplayAlertPrompt();
|
jmethodID GetDisplayAlertPrompt();
|
||||||
jmethodID GetAlertPromptButton();
|
jmethodID GetAlertPromptButton();
|
||||||
|
@ -96,6 +96,29 @@ static int AlertPromptButton() {
|
|||||||
IDCache::GetAlertPromptButton()));
|
IDCache::GetAlertPromptButton()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static jobject ToJavaCoreError(Core::System::ResultStatus result) {
|
||||||
|
static const std::map<Core::System::ResultStatus, const char*> 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 Camera::NDK::Factory* g_ndk_factory{};
|
||||||
|
|
||||||
static void TryShutdown() {
|
static void TryShutdown() {
|
||||||
@ -187,7 +210,20 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) {
|
|||||||
// Start running emulation
|
// Start running emulation
|
||||||
while (is_running) {
|
while (is_running) {
|
||||||
if (!pause_emulation) {
|
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 {
|
} else {
|
||||||
// Ensure no audio bleeds out while game is paused
|
// Ensure no audio bleeds out while game is paused
|
||||||
const float volume = Settings::values.volume;
|
const float volume = Settings::values.volume;
|
||||||
|
@ -201,4 +201,14 @@
|
|||||||
<!-- Microphone -->
|
<!-- Microphone -->
|
||||||
<string name="microphone">Microphone</string>
|
<string name="microphone">Microphone</string>
|
||||||
<string name="microphone_permission_needed">Citra needs to access your microphone to emulate the 3DS\'s microphone.\n\nAlternatively, you can also change \"Audio Input Device\" in Audio Settings.</string>
|
<string name="microphone_permission_needed">Citra needs to access your microphone to emulate the 3DS\'s microphone.\n\nAlternatively, you can also change \"Audio Input Device\" in Audio Settings.</string>
|
||||||
|
|
||||||
|
<!-- Core Errors -->
|
||||||
|
<string name="abort_button">Abort</string>
|
||||||
|
<string name="continue_button">Continue</string>
|
||||||
|
<string name="system_archive_not_found">System Archive Not Found</string>
|
||||||
|
<string name="system_archive_not_found_message">%s is missing. Please dump your system archives.\nContinuing emulation may result in crashes and bugs.</string>
|
||||||
|
<string name="system_archive_general">A system archive</string>
|
||||||
|
<string name="save_load_error">Save/Load Error</string>
|
||||||
|
<string name="fatal_error">Fatal Error</string>
|
||||||
|
<string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user