The root cause is that the JVM has taken away the main thread, caused the UIKit’s queue to stop. This env var exists in my dev environment but has never been added here.
425 lines
16 KiB
Objective-C
425 lines
16 KiB
Objective-C
#include <assert.h>
|
|
#include <dirent.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <libgen.h>
|
|
#include <spawn.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "log.h"
|
|
#include "utils.h"
|
|
|
|
#import "ios_uikit_bridge.h"
|
|
#import "JavaLauncher.h"
|
|
#import "LauncherPreferences.h"
|
|
|
|
#define fm NSFileManager.defaultManager
|
|
|
|
static char java_libs_path[2048];
|
|
static char args_path[2048];
|
|
|
|
extern char **environ;
|
|
|
|
static const char* const_progname = "java";
|
|
static const char* const_launcher = "openjdk";
|
|
static const char** const_jargs = NULL;
|
|
static const char** const_appclasspath = NULL;
|
|
static const jboolean const_javaw = JNI_FALSE;
|
|
static const jboolean const_cpwildcard = JNI_TRUE;
|
|
static const jint const_ergo_class = 0; // DEFAULT_POLICY
|
|
|
|
static int margc = -1;
|
|
static char* margv[1000];
|
|
|
|
const char *javaHome;
|
|
const char *renderer;
|
|
const char *allocmem;
|
|
const char *multidir;
|
|
|
|
char *homeDir;
|
|
|
|
NSString *renderer_pre;
|
|
NSString *allocmem_pre;
|
|
NSString *multidir_pre;
|
|
|
|
void init_loadCustomEnv() {
|
|
/* Define default env */
|
|
|
|
// I accidentally patched a bit wrong, the value should be a path containing libawt_xawt.dylib, but here is libawt.dylib path (no need to exist)
|
|
setenv("JAVA_AWT_PATH", [NSString stringWithFormat:@"%s/Frameworks/libawt.dylib", getenv("BUNDLE_PATH")].UTF8String, 1);
|
|
|
|
// Disable overloaded functions hack for Minecraft 1.17+
|
|
setenv("LIBGL_NOINTOVLHACK", "1", 1);
|
|
|
|
// Override OpenGL version to 4.1 for Zink
|
|
setenv("MESA_GL_VERSION_OVERRIDE", "4.1", 1);
|
|
|
|
// Runs JVM in a separate thread
|
|
setenv("HACK_IGNORE_START_ON_FIRST_THREAD", "1", 1);
|
|
|
|
/* Load custom env */
|
|
FILE *envFile = fopen([NSString stringWithFormat:@"%s/custom_env.txt", getenv("POJAV_HOME")].UTF8String, "r");
|
|
|
|
regLog("[Pre-init] Reading custom environment variables (custom_env.txt), opened=%d\n", envFile != NULL);
|
|
|
|
if (envFile) {
|
|
char *line = NULL;
|
|
size_t len = 0;
|
|
ssize_t read;
|
|
while ((read = getline(&line, &len, envFile)) != -1) {
|
|
if (line[0] == '#' || line[0] == '\n') continue;
|
|
if (line[read-1] == '\n') {
|
|
line[read-1] = '\0';
|
|
}
|
|
if (strchr(line, '=') != NULL) {
|
|
regLog("[Pre-init] Added custom env: %s", line);
|
|
setenv(strtok(line, "="), strtok(NULL, "="), 1);
|
|
} else {
|
|
regLog("[Pre-init] Warning: skipped empty value custom env: %s", line);
|
|
}
|
|
}
|
|
fclose(envFile);
|
|
}
|
|
}
|
|
|
|
void init_loadCustomJvmFlags() {
|
|
NSString *jvmargs = getPreference(@"java_args");
|
|
BOOL isFirstArg = YES;
|
|
for (NSString *jvmarg in [jvmargs componentsSeparatedByString:@" -"]) {
|
|
if ([jvmarg length] == 0) continue;
|
|
//margv[margc] = (char *) [jvmarg UTF8String];
|
|
|
|
++margc;
|
|
if (isFirstArg) {
|
|
isFirstArg = NO;
|
|
margv[margc] = (char *) [jvmarg UTF8String];
|
|
} else {
|
|
margv[margc] = (char *) [[@"-" stringByAppendingString:jvmarg] UTF8String];
|
|
}
|
|
|
|
NSLog(@"[JavaLauncher] Added custom JVM flag: %s", margv[margc]);
|
|
}
|
|
}
|
|
|
|
NSString* environmentFailsafes(int minVersion) {
|
|
NSString *javaHome = nil;
|
|
if (getenv("POJAV_DETECTEDJB")) {
|
|
BOOL foundJava8 = [fm fileExistsAtPath:JRE8_HOME_JB];
|
|
if (!foundJava8) {
|
|
regLog("[JavaLauncher] Java 8 wasn't found on your device. Install Java 8 for more compatibility and the mod installer.");
|
|
}
|
|
|
|
if (foundJava8 && minVersion <= 8) {
|
|
javaHome = JRE8_HOME_JB;
|
|
} else if ([fm fileExistsAtPath:JRE16_HOME_JB] && minVersion <= 16) {
|
|
javaHome = JRE16_HOME_JB;
|
|
} else if ([fm fileExistsAtPath:JRE17_HOME_JB] && minVersion <= 17) {
|
|
javaHome = JRE17_HOME_JB;
|
|
}
|
|
} else if (minVersion <= 8) {
|
|
javaHome = [NSString stringWithFormat:@"%s/jre", getenv("POJAV_HOME")];
|
|
}
|
|
|
|
if (javaHome == nil) {
|
|
showDialog(viewController, NSLocalizedString(@"Error", nil), [NSString stringWithFormat:@"Minecraft %@ requires Java %d in order to run. Please install it first.", getPreference(@"selected_version"), minVersion]);
|
|
}
|
|
|
|
return javaHome;
|
|
}
|
|
|
|
int launchJVM(NSString *username, id launchTarget, int width, int height, int minVersion) {
|
|
sprintf((char*) java_libs_path, "%s/libs", getenv("BUNDLE_PATH"));
|
|
|
|
init_loadCustomEnv();
|
|
|
|
regLog("[JavaLauncher] Beginning JVM launch\n");
|
|
|
|
NSString *javaHome_pre = getPreference(@"java_home");
|
|
|
|
// We handle unset JAVA_HOME right there
|
|
if (username == nil || getSelectedJavaVersion() < minVersion) {
|
|
NSLog(@"[JavaLauncher] Attempting to change to Java %d (actual might be higher)", minVersion);
|
|
javaHome_pre = environmentFailsafes(minVersion);
|
|
NSLog(@"[JavaLauncher] JAVA_HOME is now set to %@", javaHome_pre);
|
|
} /* else if (javaHome_pre.length == 0) {
|
|
javaHome_pre = environmentFailsafes(minVersion);
|
|
setPreference(@"java_home", javaHome_pre);
|
|
NSLog(@"[JavaLauncher] JAVA_HOME environment variable was not set. Default to %@ for future use.\n", javaHome_pre);
|
|
} */ else {
|
|
if (![NSFileManager.defaultManager fileExistsAtPath:javaHome_pre]) {
|
|
javaHome_pre = environmentFailsafes(minVersion);
|
|
setPreference(@"java_home", javaHome_pre);
|
|
NSLog(@"[JavaLauncher] Failed to locate %@. Restored default value for JAVA_HOME.", javaHome_pre);
|
|
} else {
|
|
NSLog(@"[JavaLauncher] Restored preference: JAVA_HOME is set to %@\n", javaHome_pre);
|
|
}
|
|
}
|
|
|
|
if (javaHome_pre == nil) {
|
|
return 1;
|
|
}
|
|
|
|
javaHome = javaHome_pre.UTF8String;
|
|
setenv("JAVA_HOME", javaHome, 1);
|
|
|
|
if (!getenv("POJAV_DETECTEDJB")) {
|
|
char src[2048], dst[2048];
|
|
|
|
// Symlink frameworks -> dylibs on jailed environment
|
|
mkdir(javaHome, 755);
|
|
|
|
// Symlink the skeleton part of JRE
|
|
sprintf((char *)src, "%s/jre/man", getenv("BUNDLE_PATH"));
|
|
sprintf((char *)dst, "%s/man", javaHome);
|
|
symlink(src, dst);
|
|
|
|
sprintf((char *)src, "%s/jre/lib", getenv("BUNDLE_PATH"));
|
|
sprintf((char *)dst, "%s/lib", javaHome);
|
|
mkdir(dst, 755);
|
|
|
|
DIR *d;
|
|
struct dirent *dir;
|
|
d = opendir(src);
|
|
assert(d);
|
|
int i = 0;
|
|
while ((dir = readdir(d)) != NULL) {
|
|
// Skip "." and ".."
|
|
if (i < 2) {
|
|
i++;
|
|
continue;
|
|
} else if (!strncmp(dir->d_name, "jli", 3)) {
|
|
sprintf((char *)dst, "%s/lib/jli", javaHome);
|
|
mkdir(dst, 755);
|
|
|
|
// libjli.dylib
|
|
sprintf((char *)src, "%s/Frameworks/libjli.dylib.framework/libjli.dylib", getenv("BUNDLE_PATH"));
|
|
sprintf((char *)dst, "%s/lib/jli/libjli.dylib", javaHome);
|
|
symlink(src, dst);
|
|
} else if (!strncmp(dir->d_name, "server", 6)) {
|
|
sprintf((char *)dst, "%s/lib/server", javaHome);
|
|
mkdir(dst, 755);
|
|
|
|
// libjsig.dylib
|
|
sprintf((char *)src, "%s/Frameworks/libjsig.dylib.framework/libjsig.dylib", getenv("BUNDLE_PATH"));
|
|
sprintf((char *)dst, "%s/lib/server/libjsig.dylib", javaHome);
|
|
symlink(src, dst);
|
|
|
|
// libjvm.dylib
|
|
sprintf((char *)src, "%s/Frameworks/libjvm.dylib.framework/libjvm.dylib", getenv("BUNDLE_PATH"));
|
|
sprintf((char *)dst, "%s/lib/server/libjvm.dylib", javaHome);
|
|
symlink(src, dst);
|
|
|
|
// Xusage.txt
|
|
sprintf((char *)src, "%s/jre/lib/server/Xusage.txt", getenv("BUNDLE_PATH"));
|
|
sprintf((char *)dst, "%s/lib/server/Xusage.txt", javaHome);
|
|
symlink(src, dst);
|
|
} else {
|
|
sprintf((char *)src, "%s/jre/lib/%s", getenv("BUNDLE_PATH"), dir->d_name);
|
|
sprintf((char *)dst, "%s/lib/%s", javaHome, dir->d_name);
|
|
symlink(src, dst);
|
|
}
|
|
}
|
|
closedir(d);
|
|
|
|
// Symlink dylibs
|
|
sprintf((char *)src, "%s/Frameworks", getenv("BUNDLE_PATH"));
|
|
d = opendir(src);
|
|
assert(d);
|
|
i = 0;
|
|
while ((dir = readdir(d)) != NULL) {
|
|
// Skip "." and ".."
|
|
if (i < 2) {
|
|
i++;
|
|
continue;
|
|
} else if (!strncmp(dir->d_name, "lib", 3) && strlen(dir->d_name) > 12) {
|
|
char *dylibName = strdup(dir->d_name);
|
|
dylibName[strlen(dylibName) - 10] = '\0';
|
|
sprintf((char *)src, "%s/Frameworks/%s/%s", getenv("BUNDLE_PATH"), dir->d_name, dylibName);
|
|
if (!strncmp(dir->d_name, "libjvm.dylib", 12)) {
|
|
sprintf((char *)dst, "%s/lib/server/%s", javaHome, dylibName);
|
|
} else {
|
|
sprintf((char *)dst, "%s/lib/%s", javaHome, dylibName);
|
|
}
|
|
symlink(src, dst);
|
|
dylibName[strlen(dylibName) - 11] = '.';
|
|
free(dylibName);
|
|
}
|
|
}
|
|
closedir(d);
|
|
}
|
|
|
|
renderer_pre = getPreference(@"renderer");
|
|
renderer = renderer_pre.UTF8String;
|
|
if (renderer_pre.length == 0) {
|
|
renderer_pre = @"libgl4es_114.dylib";
|
|
setPreference(@"renderer", renderer_pre);
|
|
renderer = [renderer_pre cStringUsingEncoding:NSUTF8StringEncoding];
|
|
regLog("[JavaLauncher] RENDERER environment variable was not set. Defaulting to %s for future use.\n", renderer);
|
|
} else {
|
|
regLog("[JavaLauncher] Restored preference: RENDERER is set to %s\n", renderer);
|
|
}
|
|
setenv("POJAV_RENDERER", renderer, 1);
|
|
|
|
allocmem_pre = [getPreference(@"allocated_memory") stringValue];
|
|
allocmem = [allocmem_pre cStringUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
char classpath[10000];
|
|
|
|
// "/Applications/PojavLauncher.app/libs/launcher.jar:/Applications/PojavLauncher.app/libs/ExagearApacheCommons.jar:/Applications/PojavLauncher.app/libs/gson-2.8.6.jar:/Applications/PojavLauncher.app/libs/jsr305.jar:/Applications/PojavLauncher.app/libs/lwjgl3-minecraft.jar";
|
|
|
|
// Generate classpath
|
|
DIR *d;
|
|
struct dirent *dir;
|
|
d = opendir(java_libs_path);
|
|
int cplen = -2;
|
|
if (d) {
|
|
// cplen += sprintf(classpath + cplen, "-Xbootclasspath/a:");
|
|
while ((dir = readdir(d)) != NULL) {
|
|
if (cplen < 0) {
|
|
++cplen;
|
|
continue;
|
|
}
|
|
cplen += sprintf(classpath + cplen, "%s/%s:", java_libs_path, dir->d_name);
|
|
}
|
|
classpath[cplen-1] = '\0';
|
|
closedir(d);
|
|
}
|
|
debugLog("[JavaLauncher] Classpath generated: %s", classpath);
|
|
|
|
// Check if JVM restarts
|
|
char *frameworkPath, *javaPath, *jnaLibPath, *userDir, *userHome, *memMin, *memMax, *arcDNS;
|
|
asprintf(&frameworkPath, "-Djava.library.path=%1$s/Frameworks:%1$s/Frameworks/libOSMesaOverride.dylib.framework:%1$s/Frameworks/libMoltenVK.dylib.framework", getenv("BUNDLE_PATH"));
|
|
asprintf(&javaPath, "%s/bin/java", javaHome);
|
|
asprintf(&jnaLibPath, "-Djna.boot.library.path=%s/Frameworks/libjnidispatch.dylib.framework", getenv("BUNDLE_PATH"));
|
|
asprintf(&userDir, "-Duser.dir=%s", getenv("POJAV_GAME_DIR"));
|
|
asprintf(&userHome, "-Duser.home=%s", getenv("POJAV_HOME"));
|
|
asprintf(&memMin, "-Xms%sM", allocmem);
|
|
asprintf(&memMax, "-Xmx%sM", allocmem);
|
|
asprintf(&arcDNS, "-javaagent:%s/arc_dns_injector.jar=23.95.137.176", java_libs_path);
|
|
NSLog(@"[JavaLauncher] Java executable path: %s", javaPath);
|
|
setenv("JAVA_EXT_EXECNAME", javaPath, 1);
|
|
|
|
margv[++margc] = javaPath;
|
|
margv[++margc] = "-XstartOnFirstThread";
|
|
margv[++margc] = "-Djava.system.class.loader=net.kdt.pojavlaunch.PojavClassLoader";
|
|
margv[++margc] = memMin;
|
|
margv[++margc] = memMax;
|
|
margv[++margc] = frameworkPath;
|
|
margv[++margc] = jnaLibPath;
|
|
margv[++margc] = userDir;
|
|
margv[++margc] = userHome;
|
|
margv[++margc] = "-Dorg.lwjgl.system.allocator=system";
|
|
margv[++margc] = "-Dlog4j2.formatMsgNoLookups=true";
|
|
if([getPreference(@"arccapes_enable") boolValue]) {
|
|
margv[++margc] = arcDNS;
|
|
}
|
|
NSString *selectedAccount = getPreference(@"internal_selected_account");
|
|
if (selectedAccount != nil) {
|
|
margv[++margc] = (char *) [NSString stringWithFormat:@"-Dpojav.selectedAccount=%@", selectedAccount].UTF8String;
|
|
}
|
|
|
|
// Load java
|
|
char libjlipath8[2048]; // java 8
|
|
char libjlipath16[2048]; // java 16+ (?)
|
|
sprintf(libjlipath8, "%s/lib/jli/libjli.dylib", javaHome);
|
|
sprintf(libjlipath16, "%s/lib/libjli.dylib", javaHome);
|
|
setenv("INTERNAL_JLI_PATH", libjlipath16, 1);
|
|
void* libjli = dlopen(libjlipath16, RTLD_LAZY | RTLD_GLOBAL);
|
|
if (!libjli) {
|
|
debugLog("[Init] Can't load %s, trying %s", libjlipath16, libjlipath8);
|
|
setenv("INTERNAL_JLI_PATH", libjlipath8, 1);
|
|
libjli = dlopen(libjlipath8, RTLD_LAZY | RTLD_GLOBAL);
|
|
if (!libjli) {
|
|
debugLog("[Init] JLI lib = NULL: %s", dlerror());
|
|
return -1;
|
|
}
|
|
|
|
// Setup Caciocavallo
|
|
margv[++margc] = "-Djava.awt.headless=false";
|
|
margv[++margc] = "-Dcacio.font.fontmanager=sun.awt.X11FontManager";
|
|
margv[++margc] = "-Dcacio.font.fontscaler=sun.font.FreetypeFontScaler";
|
|
margv[++margc] = "-Dswing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel";
|
|
margv[++margc] = "-Dawt.toolkit=net.java.openjdk.cacio.ctc.CTCToolkit";
|
|
margv[++margc] = "-Djava.awt.graphicsenv=net.java.openjdk.cacio.ctc.CTCGraphicsEnvironment";
|
|
|
|
// Generate Caciocavallo bootclasspath
|
|
char cacio_libs_path[2048];
|
|
char cacio_classpath[8192];
|
|
sprintf((char*) cacio_libs_path, "%s/libs_caciocavallo", getenv("BUNDLE_PATH"));
|
|
cplen = sprintf(cacio_classpath, "-Xbootclasspath/p");
|
|
d = opendir(cacio_libs_path);
|
|
int skip = 2;
|
|
if (d) {
|
|
while ((dir = readdir(d)) != NULL) {
|
|
if (skip > 0) {
|
|
--skip;
|
|
continue;
|
|
}
|
|
cplen += sprintf(cacio_classpath + cplen, ":%s/%s", cacio_libs_path, dir->d_name);
|
|
}
|
|
closedir(d);
|
|
}
|
|
margv[++margc] = cacio_classpath;
|
|
} else {
|
|
margv[++margc] = "--add-opens=java.base/java.net=ALL-UNNAMED";
|
|
|
|
// TODO: workaround, will be removed once the startup part works without PLaunchApp
|
|
margv[++margc] = "--add-exports=cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED";
|
|
}
|
|
|
|
if ([launchTarget isKindOfClass:NSDictionary.class]) {
|
|
for (NSString *arg in launchTarget[@"arguments"][@"jvm_processed"]) {
|
|
margv[++margc] = (char *)arg.UTF8String;
|
|
}
|
|
}
|
|
|
|
init_loadCustomJvmFlags();
|
|
regLog("[Init] Found JLI lib");
|
|
|
|
margv[++margc] = "-cp";
|
|
margv[++margc] = classpath;
|
|
margv[++margc] = "net.kdt.pojavlaunch.PojavLauncher";
|
|
|
|
if (username == nil) {
|
|
margv[++margc] = ".LaunchJAR";
|
|
} else {
|
|
margv[++margc] = (char *)username.UTF8String;
|
|
}
|
|
if ([launchTarget isKindOfClass:NSDictionary.class]) {
|
|
margv[++margc] = (char *)[launchTarget[@"id"] UTF8String];
|
|
} else {
|
|
margv[++margc] = (char *)[launchTarget UTF8String];
|
|
}
|
|
margv[++margc] = (char *)[NSString stringWithFormat:@"%dx%d", width, height].UTF8String;
|
|
|
|
pJLI_Launch = (JLI_Launch_func *)dlsym(libjli, "JLI_Launch");
|
|
|
|
if (NULL == pJLI_Launch) {
|
|
regLog("[Init] JLI_Launch = NULL");
|
|
return -2;
|
|
}
|
|
|
|
regLog("[Init] Calling JLI_Launch");
|
|
|
|
/*
|
|
for (int i = 0; i < margc; i++) {
|
|
debugLog("Arg=%s", margv[i]);
|
|
}
|
|
*/
|
|
|
|
return pJLI_Launch(++margc, margv,
|
|
0, NULL, // sizeof(const_jargs) / sizeof(char *), const_jargs,
|
|
0, NULL, // sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
|
|
// PojavLancher: fixme: are these wrong?
|
|
"1.8.0-internal",
|
|
"1.8",
|
|
|
|
"java", "openjdk",
|
|
/* (const_jargs != NULL) ? JNI_TRUE : */ JNI_FALSE,
|
|
JNI_TRUE, JNI_FALSE, JNI_TRUE);
|
|
}
|