diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java index f488f66c4..cf10077be 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/ExitActivity.java @@ -4,7 +4,6 @@ import static net.kdt.pojavlaunch.Tools.shareLog; import android.annotation.SuppressLint; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; @@ -20,22 +19,26 @@ public class ExitActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - int code = -1; + int code = -1; boolean isSignal = false; Bundle extras = getIntent().getExtras(); if(extras != null) { code = extras.getInt("code",-1); + isSignal = extras.getBoolean("isSignal", false); } + int message = isSignal ? R.string.mcn_signal_title : R.string.mcn_exit_title; + new AlertDialog.Builder(this) - .setMessage(getString(R.string.mcn_exit_title,code)) + .setMessage(getString(message,code)) .setPositiveButton(R.string.main_share_logs, (dialog, which) -> shareLog(this)) .setOnDismissListener(dialog -> ExitActivity.this.finish()) .show(); } - public static void showExitMessage(Context ctx, int code) { + public static void showExitMessage(Context ctx, int code, boolean isSignal) { Intent i = new Intent(ctx,ExitActivity.class); i.putExtra("code",code); + i.putExtra("isSignal", isSignal); i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ctx.startActivity(i); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java index 18f15590e..fb85224ef 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java @@ -572,6 +572,5 @@ public class JREUtils { static { System.loadLibrary("pojavexec"); System.loadLibrary("pojavexec_awt"); - System.loadLibrary("istdio"); } } diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index 608894c39..3ac7a1f72 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -20,12 +20,15 @@ LOCAL_SRC_FILES := tinywrapper/main.c tinywrapper/string_utils.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/tinywrapper include $(BUILD_SHARED_LIBRARY) +$(call import-module,prefab/bytehook) +LOCAL_PATH := $(HERE_PATH) include $(CLEAR_VARS) # Link GLESv2 for test LOCAL_LDLIBS := -ldl -llog -landroid # -lGLESv2 LOCAL_MODULE := pojavexec +LOCAL_SHARED_LIBRARIES := bytehook # LOCAL_CFLAGS += -DDEBUG # -DGLES_TEST LOCAL_SRC_FILES := \ @@ -40,6 +43,7 @@ LOCAL_SRC_FILES := \ input_bridge_v3.c \ jre_launcher.c \ utils.c \ + stdio_is.c \ driver_helper/nsbypass.c ifeq ($(TARGET_ARCH_ABI),arm64-v8a) @@ -56,16 +60,6 @@ LOCAL_LDFLAGS := -z global include $(BUILD_SHARED_LIBRARY) #endif -$(call import-module,prefab/bytehook) -LOCAL_PATH := $(HERE_PATH) - -include $(CLEAR_VARS) -LOCAL_MODULE := istdio -LOCAL_SHARED_LIBRARIES := bytehook -LOCAL_SRC_FILES := \ - stdio_is.c -include $(BUILD_SHARED_LIBRARY) - include $(CLEAR_VARS) LOCAL_MODULE := pojavexec_awt LOCAL_SRC_FILES := \ diff --git a/app_pojavlauncher/src/main/jni/jre_launcher.c b/app_pojavlauncher/src/main/jni/jre_launcher.c index 823abf063..d905effc1 100644 --- a/app_pojavlauncher/src/main/jni/jre_launcher.c +++ b/app_pojavlauncher/src/main/jni/jre_launcher.c @@ -65,6 +65,62 @@ typedef jint JLI_Launch_func(int argc, char ** argv, /* main argc, argc */ jint ergo /* ergonomics class policy */ ); +struct { + sigset_t tracked_sigset; + int pipe[2]; +} abrt_waiter_data; + +_Noreturn extern void nominal_exit(int code, bool is_signal); + +_Noreturn static void* abrt_waiter_thread(void* extraArg) { + // Don't allow this thread to receive signals this thread is tracking. + // We should only receive them externally. + pthread_sigmask(SIG_BLOCK, &abrt_waiter_data.tracked_sigset, NULL); + int signal; + // Block for reading the signal ID until it arrives + read(abrt_waiter_data.pipe[0], &signal, sizeof(int)); + // Die + nominal_exit(signal, true); +} + +_Noreturn static void abrt_waiter_handler(int signal) { + // Write the final signal into the pipe and block forever. + write(abrt_waiter_data.pipe[1], &signal, sizeof(int)); + while(1) {}; +}; + +static void abrt_waiter_setup() { + // Only abort on SIGABRT as the JVM either emits SIGABRT or SIGKILL (which we can't catch) + // when a fatal crash occurs. Still, keep expandability if we would want to add more + // user-friendly fatal signals in the future. + const static int tracked_signals[] = {SIGABRT}; + const static int ntracked = (sizeof(tracked_signals) / sizeof(tracked_signals[0])); + struct sigaction sigactions[ntracked]; + sigemptyset(&abrt_waiter_data.tracked_sigset); + for(size_t i = 0; i < ntracked; i++) { + sigaddset(&abrt_waiter_data.tracked_sigset, tracked_signals[i]); + sigactions[i].sa_handler = abrt_waiter_handler; + } + if(pipe(abrt_waiter_data.pipe) != 0) { + printf("Failed to set up aborter pipe: %s\n", strerror(errno)); + return; + } + pthread_t waiter_thread; int result; + if((result = pthread_create(&waiter_thread, NULL, abrt_waiter_thread, NULL)) != 0) { + printf("Failed to start up waiter thread: %s", strerror(result)); + for(int i = 0; i < 2; i++) close(abrt_waiter_data.pipe[i]); + return; + } + // Only set the sigactions *after* we have already set up the pipe and the thread. + for(size_t i = 0; i < ntracked; i++) { + if(sigaction(tracked_signals[i], &sigactions[i], NULL) != 0) { + // Not returning here because we may have set some handlers successfully. + // Some handling is better than no handling. + printf("Failed to set signal hander for signal %i: %s", i, strerror(errno)); + } + } +} + static jint launchJVM(int margc, char** margv) { void* libjli = dlopen("libjli.so", RTLD_LAZY | RTLD_GLOBAL); @@ -80,6 +136,8 @@ static jint launchJVM(int margc, char** margv) { else clean_sa.sa_handler = SIG_DFL; sigaction(sigid, &clean_sa, NULL); } + // Set up the thread that will abort the launcher with an user-facing dialog on a signal. + abrt_waiter_setup(); // Boardwalk: silence // LOGD("JLI lib = %x", (int)libjli); diff --git a/app_pojavlauncher/src/main/jni/stdio_is.c b/app_pojavlauncher/src/main/jni/stdio_is.c index 2ef90a9ce..2bd61aeca 100644 --- a/app_pojavlauncher/src/main/jni/stdio_is.c +++ b/app_pojavlauncher/src/main/jni/stdio_is.c @@ -9,6 +9,7 @@ #include #include #include +#include // // Created by maks on 17.02.21. @@ -19,7 +20,6 @@ static volatile jclass exitTrap_exitClass; static volatile jmethodID exitTrap_staticMethod; static JavaVM *exitTrap_jvm; -static JavaVM *stdiois_jvm; static int pfd[2]; static pthread_t logger; static jmethodID logger_onEventLogged; @@ -37,19 +37,11 @@ static bool recordBuffer(char* buf, ssize_t len) { return true; } -JNIEXPORT jint JNI_OnLoad(JavaVM* vm, __attribute((unused)) void* reserved) { - stdiois_jvm = vm; - JNIEnv *env; - (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4); - jclass eventLogListener = (*env)->FindClass(env, "net/kdt/pojavlaunch/Logger$eventLogListener"); - logger_onEventLogged = (*env)->GetMethodID(env, eventLogListener, "onEventLogged", "(Ljava/lang/String;)V"); - return JNI_VERSION_1_4; -} - static void *logger_thread() { JNIEnv *env; jstring writeString; - (*stdiois_jvm)->AttachCurrentThread(stdiois_jvm, &env, NULL); + JavaVM* dvm = pojav_environ->dalvikJavaVMPtr; + (*dvm)->AttachCurrentThread(dvm, &env, NULL); ssize_t rsize; char buf[2050]; while((rsize = read(pfd[0], buf, sizeof(buf)-1)) > 0) { @@ -64,7 +56,7 @@ static void *logger_thread() { (*env)->DeleteLocalRef(env, writeString); } } - (*stdiois_jvm)->DetachCurrentThread(stdiois_jvm); + (*dvm)->DetachCurrentThread(dvm); return NULL; } JNIEXPORT void JNICALL @@ -74,6 +66,10 @@ Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass latestlog_fd = -1; close(localfd); } + if(logger_onEventLogged == NULL) { + jclass eventLogListener = (*env)->FindClass(env, "net/kdt/pojavlaunch/Logger$eventLogListener"); + logger_onEventLogged = (*env)->GetMethodID(env, eventLogListener, "onEventLogged", "(Ljava/lang/String;)V"); + } jclass ioeClass = (*env)->FindClass(env, "java/io/IOException"); @@ -109,7 +105,7 @@ Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass typedef void (*exit_func)(int); -_Noreturn static void nominal_exit(int code) { +_Noreturn void nominal_exit(int code, bool is_signal) { JNIEnv *env; jint errorCode = (*exitTrap_jvm)->GetEnv(exitTrap_jvm, (void**)&env, JNI_VERSION_1_6); if(errorCode == JNI_EDETACHED) { @@ -124,7 +120,7 @@ _Noreturn static void nominal_exit(int code) { if(code != 0) { // Exit code 0 is pretty established as "eh it's fine" // so only open the GUI if the code is != 0 - (*env)->CallStaticVoidMethod(env, exitTrap_exitClass, exitTrap_staticMethod, exitTrap_ctx, code); + (*env)->CallStaticVoidMethod(env, exitTrap_exitClass, exitTrap_staticMethod, exitTrap_ctx, code, is_signal); } // Delete the reference, not gonna need 'em later anyway (*env)->DeleteGlobalRef(env, exitTrap_ctx); @@ -155,7 +151,7 @@ static void custom_exit(int code) { } exit_tripped = true; // Perform a nominal exit, as we expect. - nominal_exit(code); + nominal_exit(code, false); BYTEHOOK_POP_STACK(); } @@ -165,14 +161,14 @@ static void custom_atexit() { return; } exit_tripped = true; - nominal_exit(0); + nominal_exit(0, false); } JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_setupExitTrap(JNIEnv *env, __attribute((unused)) jclass clazz, jobject context) { exitTrap_ctx = (*env)->NewGlobalRef(env,context); (*env)->GetJavaVM(env,&exitTrap_jvm); exitTrap_exitClass = (*env)->NewGlobalRef(env,(*env)->FindClass(env,"net/kdt/pojavlaunch/ExitActivity")); - exitTrap_staticMethod = (*env)->GetStaticMethodID(env,exitTrap_exitClass,"showExitMessage","(Landroid/content/Context;I)V"); + exitTrap_staticMethod = (*env)->GetStaticMethodID(env,exitTrap_exitClass,"showExitMessage","(Landroid/content/Context;IZ)V"); if(bytehook_init(BYTEHOOK_MODE_AUTOMATIC, false) == BYTEHOOK_STATUS_CODE_OK) { bytehook_hook_all(NULL, diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index 96850becc..67d1675d4 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -93,6 +93,7 @@ Application/Game exited with code %d, check latestlog.txt for more details. + Application/Game aborted by fatal signal %d, check latestlog.txt for more details. Are you sure want to force close? Zink (Vulkan) renderer is not supported on this device!