From fbe587bea5da3331c2867dfcb46001bf98462fd0 Mon Sep 17 00:00:00 2001 From: artdeell Date: Mon, 1 May 2023 23:15:32 +0300 Subject: [PATCH] Stdio logging through C --- .../src/main/java/com/kdt/LoggerView.java | 9 +- .../pojavlaunch/JavaGUILauncherActivity.java | 2 +- .../main/java/net/kdt/pojavlaunch/Logger.java | 74 +------------ .../net/kdt/pojavlaunch/MainActivity.java | 2 +- .../kdt/pojavlaunch/multirt/MultiRTUtils.java | 1 - .../net/kdt/pojavlaunch/utils/JREUtils.java | 2 - app_pojavlauncher/src/main/jni/stdio_is.c | 103 ++++++++++++++---- 7 files changed, 95 insertions(+), 98 deletions(-) diff --git a/app_pojavlauncher/src/main/java/com/kdt/LoggerView.java b/app_pojavlauncher/src/main/java/com/kdt/LoggerView.java index b77ffeb68..9d34e9209 100644 --- a/app_pojavlauncher/src/main/java/com/kdt/LoggerView.java +++ b/app_pojavlauncher/src/main/java/com/kdt/LoggerView.java @@ -60,7 +60,13 @@ public class LoggerView extends ConstraintLayout { mToggleButton.setOnCheckedChangeListener( (compoundButton, isChecked) -> { mLogTextView.setVisibility(isChecked ? VISIBLE : GONE); - if(!isChecked) mLogTextView.setText(""); + if(isChecked) { + Logger.setLogListener(mLogListener); + }else{ + mLogTextView.setText(""); + Logger.setLogListener(null); // Makes the JNI code be able to skip expensive logger callbacks + // NOTE: was tested by rapidly smashing the log on/off button, no sync issues found :) + } }); mToggleButton.setChecked(false); @@ -80,7 +86,6 @@ public class LoggerView extends ConstraintLayout { }); }; - Logger.getInstance().setLogListener(mLogListener); } } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java index b7cb2d600..5ae5ce981 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/JavaGUILauncherActivity.java @@ -55,7 +55,7 @@ public class JavaGUILauncherActivity extends BaseActivity implements View.OnTouc super.onCreate(savedInstanceState); setContentView(R.layout.activity_java_gui_launcher); - Logger.getInstance().reset(); + Logger.begin(new File(Tools.DIR_GAME_HOME, "latestlog.txt").getAbsolutePath()); mTouchCharInput = findViewById(R.id.awt_touch_char); mTouchCharInput.setCharacterSender(new AwtCharSender()); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Logger.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Logger.java index 9432559d2..a2b696b71 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Logger.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Logger.java @@ -2,75 +2,24 @@ package net.kdt.pojavlaunch; import androidx.annotation.Keep; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.lang.ref.WeakReference; - /** Singleton class made to log on one file * The singleton part can be removed but will require more implementation from the end-dev */ @Keep public class Logger { - private static volatile Logger sLoggerSingleton = null; - /* Instance variables */ - private final File mLogFile; - private PrintStream mLogStream; - private WeakReference mLogListenerWeakReference = null; - - /* No public construction */ - private Logger(){ - mLogFile = new File(Tools.DIR_GAME_HOME, "latestlog.txt"); - // Make a new instance of the log file - // Default PrintStream constructor will overwrite the file for us - try { - mLogStream = new PrintStream(mLogFile.getAbsolutePath()); - }catch (IOException e){e.printStackTrace();} - - } + private static final Logger dummyLogger = new Logger(); public static Logger getInstance(){ - if(sLoggerSingleton == null){ - synchronized(Logger.class){ - if(sLoggerSingleton == null){ - sLoggerSingleton = new Logger(); - } - } - } - return sLoggerSingleton; + return dummyLogger; } - /** Print the text to the log file if not censored */ - public void appendToLog(String text){ - if(shouldCensorLog(text)) return; - appendToLogUnchecked(text); - } + public static native void appendToLog(String text); - /** Print the text to the log file, no china censoring there */ - public void appendToLogUnchecked(String text){ - mLogStream.println(text); - notifyLogListener(text); - } /** Reset the log file, effectively erasing any previous logs */ - public void reset(){ - try{ - //Refer to line 30 - mLogStream = new PrintStream(mLogFile.getAbsolutePath()); - }catch (IOException e){ e.printStackTrace();} - } - - /** - * Perform various checks to see if the log is safe to print - * Subclasses may want to override this behavior - * @param text The text to check - * @return Whether the log should be censored - */ - private static boolean shouldCensorLog(String text){ - return text.contains("Session ID is"); - } + public static native void begin(String logFilePath); /** Small listener for anything listening to the log */ public interface eventLogListener { @@ -78,18 +27,5 @@ public class Logger { } /** Link a log listener to the logger */ - public void setLogListener(eventLogListener logListener){ - this.mLogListenerWeakReference = new WeakReference<>(logListener); - } - - /** Notifies the event listener, if it exists */ - private void notifyLogListener(String text){ - if(mLogListenerWeakReference == null) return; - eventLogListener logListener = mLogListenerWeakReference.get(); - if(logListener == null){ - mLogListenerWeakReference = null; - return; - } - logListener.onEventLogged(text); - } + public static native void setLogListener(eventLogListener logListener); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java index 9cc5ad604..6b8384d86 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java @@ -131,7 +131,7 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); try { - Logger.getInstance().reset(); + Logger.begin(new File(Tools.DIR_GAME_HOME, "latestlog.txt").getAbsolutePath()); // FIXME: is it safe for multi thread? GLOBAL_CLIPBOARD = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); touchCharInput.setCharacterSender(new LwjglCharSender()); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java index 071c1b735..76ea8b1f0 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/multirt/MultiRTUtils.java @@ -35,7 +35,6 @@ public class MultiRTUtils { private static final File RUNTIME_FOLDER = new File(Tools.MULTIRT_HOME); private static final String JAVA_VERSION_STR = "JAVA_VERSION=\""; private static final String OS_ARCH_STR = "OS_ARCH=\""; - private static String sSelectedRuntimeName; public static List getRuntimes() { if(!RUNTIME_FOLDER.exists() && !RUNTIME_FOLDER.mkdirs()) { 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 7509243d1..7bff5727d 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 @@ -96,7 +96,6 @@ public class JREUtils { public static void redirectAndPrintJRELog() { Log.v("jrelog","Log starts here"); - JREUtils.logToLogger(Logger.getInstance()); new Thread(new Runnable(){ int failTime = 0; ProcessBuilder logcatPb; @@ -545,7 +544,6 @@ public class JREUtils { } } public static native int chdir(String path); - public static native void logToLogger(final Logger logger); public static native boolean dlopen(String libPath); public static native void setLdLibraryPath(String ldLibraryPath); public static native void setupBridgeWindow(Object surface); diff --git a/app_pojavlauncher/src/main/jni/stdio_is.c b/app_pojavlauncher/src/main/jni/stdio_is.c index dd74040d4..c5766eb99 100644 --- a/app_pojavlauncher/src/main/jni/stdio_is.c +++ b/app_pojavlauncher/src/main/jni/stdio_is.c @@ -5,46 +5,74 @@ #include #include #include +#include +#include +#include // // Created by maks on 17.02.21. // -static JavaVM *stdiois_jvm; -static volatile jmethodID log_cbMethod; -static volatile jobject log_cbObject; + static volatile jobject exitTrap_ctx; 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; +static volatile jobject logListener = NULL; +static int latestlog_fd = -1; + +static bool recordBuffer(char* buf, ssize_t len) { + if(strstr(buf, "Session ID is")) return false; + if(latestlog_fd != -1) { + write(latestlog_fd, buf, len); + fdatasync(latestlog_fd); + } + 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 str; + jstring writeString; (*stdiois_jvm)->AttachCurrentThread(stdiois_jvm, &env, NULL); ssize_t rsize; - char buf[2048]; + char buf[2050]; while((rsize = read(pfd[0], buf, sizeof(buf)-1)) > 0) { if(buf[rsize-1]=='\n') { rsize=rsize-1; } buf[rsize]=0x00; - str = (*env)->NewStringUTF(env,buf); - (*env)->CallVoidMethod(env, log_cbObject, log_cbMethod, str); - (*env)->DeleteLocalRef(env,str); + if(recordBuffer(buf, rsize) && logListener != NULL) { + writeString = (*env)->NewStringUTF(env, buf); + (*env)->CallVoidMethod(env, logListener, logger_onEventLogged, writeString); + (*env)->DeleteLocalRef(env, writeString); + } } - (*env)->DeleteGlobalRef(env, log_cbMethod); - (*env)->DeleteGlobalRef(env, log_cbObject); (*stdiois_jvm)->DetachCurrentThread(stdiois_jvm); return NULL; } JNIEXPORT void JNICALL -Java_net_kdt_pojavlaunch_utils_JREUtils_logToLogger(JNIEnv *env, __attribute((unused)) jclass clazz, jobject javaLogger) { +Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass clazz, jstring logPath) { // TODO: implement logToActivity() - jclass loggableActivityClass = (*env)->FindClass(env,"net/kdt/pojavlaunch/Logger"); - log_cbMethod = (*env)->GetMethodID(env, loggableActivityClass, "appendToLog", "(Ljava/lang/String;)V"); - (*env)->GetJavaVM(env,&stdiois_jvm); - log_cbObject = (*env)->NewGlobalRef(env, javaLogger); + if(latestlog_fd != -1) { + int localfd = latestlog_fd; + latestlog_fd = -1; + close(localfd); + } + jclass ioeClass = (*env)->FindClass(env, "java/io/IOException"); + setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered @@ -54,17 +82,26 @@ Java_net_kdt_pojavlaunch_utils_JREUtils_logToLogger(JNIEnv *env, __attribute((un dup2(pfd[1], 1); dup2(pfd[1], 2); + /* open latestlog.txt for writing */ + const char* logFilePath = (*env)->GetStringUTFChars(env, logPath, NULL); + latestlog_fd = open(logFilePath, O_WRONLY | O_TRUNC | O_CREAT | O_NOATIME, 666); + if(latestlog_fd == -1) { + latestlog_fd = 0; + (*env)->ThrowNew(env, ioeClass, strerror(errno)); + return; + } + (*env)->ReleaseStringUTFChars(env, logPath, logFilePath); + /* spawn the logging thread */ - if(pthread_create(&logger, 0, logger_thread, 0) != 0) { - jstring str = (*env)->NewStringUTF(env,"Failed to start logging!"); - (*env)->CallVoidMethod(env, log_cbObject, log_cbMethod, str); - (*env)->DeleteLocalRef(env,str); - (*env)->DeleteGlobalRef(env, log_cbMethod); - (*env)->DeleteGlobalRef(env, log_cbObject); + int result = pthread_create(&logger, 0, logger_thread, 0); + if(result != 0) { + close(latestlog_fd); + (*env)->ThrowNew(env, ioeClass, strerror(result)); } pthread_detach(logger); - } + + void (*old_exit)(int code); void custom_exit(int code) { if(code != 0) { @@ -87,3 +124,25 @@ JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_setupExitTrap(JNI xhook_register(".*\\.so$", "exit", custom_exit, (void **) &old_exit); xhook_refresh(1); } + +JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_Logger_appendToLog(JNIEnv *env, __attribute((unused)) jclass clazz, jstring text) { + jsize appendStringLength = (*env)->GetStringUTFLength(env, text); + char newChars[appendStringLength+2]; + (*env)->GetStringUTFRegion(env, text, 0, appendStringLength, newChars); + newChars[appendStringLength] = '\n'; + newChars[appendStringLength+1] = 0; + if(recordBuffer(newChars, appendStringLength+1) && logListener != NULL) { + (*env)->CallVoidMethod(env, logListener, logger_onEventLogged, text); + } +} + +JNIEXPORT void JNICALL +Java_net_kdt_pojavlaunch_Logger_setLogListener(JNIEnv *env, __attribute((unused)) jclass clazz, jobject log_listener) { + jobject logListenerLocal = logListener; + if(log_listener == NULL) { + logListener = NULL; + }else{ + logListener = (*env)->NewGlobalRef(env, log_listener); + } + if(logListenerLocal != NULL) (*env)->DeleteGlobalRef(env, logListenerLocal); +} \ No newline at end of file