Files
PojavLauncher_iOS/Natives/JavaLauncher.m
T
Duy Tran KhanhandDuy Tran Khanh 8d6d66dd94 Feats: external runtime, update LWJGL to 3.3.1 (#579)
* Feat: external runtime manager

Currently missing: mmap hook

* we're past this ui todo

* feat: Bypass Library Validation (untested)

This bypass works because of JIT enabled, so the process can patch
anything loaded in the memory. Unsigned dylib works with JIT too.

* Fix `could not get real path` on iOS 14.x

iOS 15+ is untested, it may need patching in dyld instead of libSystem

* Make redirectFunction syscall-friendly

Replaced builtin_mprotect and vm_protect with a simple syscall to
_kernelrpc_mach_vm_protect_trap

* Add dyld hook back for iOS 15+, auto symlink xawt lib

* Cleanup Tools.java:showError+comments

Use System.exit(1) to drop to Game Exited screen instead of presenting
alert dialog on exception handled.

* Patch both to bypass codesigning at best

* Proper way of ignoring SIGBUS during patch

Turns out other background thread might also be calling inside dirty
page, so let's SIG_IGN them all until page permission is back to RX.

* Feat(install jar): auto detect java version

Rename Default version to Preferred version to match the behavior or
Install .jar detection: only override if selected version > required
version.
Add UnzipKit just to check jar file

Refactor(showDialog): remove viewController parameter, now use its own
ViewController to present.

* Feat: update LWJGL to 3.3.1
(tinygl4angle) add alias for GL_KHR_debug functions
Include nanovg for OneConfig mod
Move all LWJGLX classes to lwjgl3 repo
Set org.lwjgl.util.NoChecks=true to improve performance

* Fix: Forge 1.20 crashes (wrong artifact path)
Now use json-provided artifact path instead of generating one
Make lwjgl fat jar again

* Fix: handle null library artifact

* Fix: fatal glfw callback cleanup

* Build: fix copying LWJGL libraries

* Perf: set default settings

* Fix: incorrect SolClient useStackQueue check

* Feat: builtin runtime installer

Refactor parts of LauncherNavigationController.m
Workaround(LogView): disable animation on update to mitigate overload

* Fix: random game directory failures

This is caused by having current working directory unchanged after
re-making symlink.

* Fixes: TL-style artifact, update arc_dns_injector

Change pojavInit names

* Fix: 1.13+ isUseStackQueueCall check

* Build: update liblwjgl.dylib in attempt to fix ffi_closure_alloc

* Fix and improvements to the logger

- Dynamically get stdout/stderr fileno instead of fixed value
- Improve file migration logging
- Improve dyld bypass logging

* Fix(pickers): file permission issue

---------

Co-authored-by: Duy Tran Khanh <[email protected]>
2023-06-28 21:49:15 +07:00

304 lines
13 KiB
Objective-C

#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 "utils.h"
#import "ios_uikit_bridge.h"
#import "JavaLauncher.h"
#import "LauncherPreferences.h"
#define fm NSFileManager.defaultManager
extern char **environ;
void init_loadDefaultEnv() {
/* Define default env */
// Silent Caciocavallo NPE error in locating Android-only lib
setenv("LD_LIBRARY_PATH", "", 1);
// Ignore mipmap for performance(?) seems does not affect iOS
//setenv("LIBGL_MIPMAP", "3", 1);
// Disable overloaded functions hack for Minecraft 1.17+
setenv("LIBGL_NOINTOVLHACK", "1", 1);
// Fix white color on banner and sheep, since GL4ES 1.1.5
setenv("LIBGL_NORMALIZE", "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);
}
void init_loadCustomEnv() {
NSString *envvars = getPreference(@"env_variables");
if (envvars == nil) return;
NSLog(@"[JavaLauncher] Reading custom environment variables");
for (NSString *line in [envvars componentsSeparatedByCharactersInSet:NSCharacterSet.whitespaceCharacterSet]) {
if (![line containsString:@"="]) {
NSLog(@"[JavaLauncher] Warning: skipped empty value custom env variable: %@", line);
continue;
}
NSRange range = [line rangeOfString:@"="];
NSString *key = [line substringToIndex:range.location];
NSString *value = [line substringFromIndex:range.location+range.length];
setenv(key.UTF8String, value.UTF8String, 1);
NSLog(@"[JavaLauncher] Added custom env variable: %@", line);
}
}
void init_loadCustomJvmFlags(int* argc, const char** argv) {
NSString *jvmargs = getPreference(@"java_args");
if (jvmargs == nil) return;
BOOL isFirstArg = YES;
NSLog(@"[JavaLauncher] Reading custom JVM flags");
for (NSString *jvmarg in [jvmargs componentsSeparatedByString:@" -"]) {
if ([jvmarg length] == 0) continue;
//margv[margc] = (char *) [jvmarg UTF8String];
++*argc;
if (isFirstArg) {
isFirstArg = NO;
argv[*argc] = jvmarg.UTF8String;
} else {
argv[*argc] = [@"-" stringByAppendingString:jvmarg].UTF8String;
}
NSLog(@"[JavaLauncher] Added custom JVM flag: %s", argv[*argc]);
}
}
int launchJVM(NSString *username, id launchTarget, int width, int height, int minVersion) {
NSLog(@"[JavaLauncher] Beginning JVM launch");
init_loadDefaultEnv();
init_loadCustomEnv();
NSLog(@"[JavaLauncher] Looking for Java %d or later", minVersion);
NSString *defaultJRETag;
if ([launchTarget isKindOfClass:NSDictionary.class]) {
if (minVersion <= 8) {
defaultJRETag = @"1_16_5_older";
} else {
defaultJRETag = @"1_17_newer";
}
} else {
defaultJRETag = @"install_jar";
}
NSString *javaHome = getSelectedJavaHome(defaultJRETag, minVersion);
if (javaHome == nil) {
UIKit_returnToSplitView();
BOOL isInstallJar = [defaultJRETag isEqualToString:@"install_jar"];
showDialog(localize(@"Error", nil), [NSString stringWithFormat:localize(@"java.error.missing_runtime", nil),
isInstallJar ? [launchTarget lastPathComponent] : getPreference(@"selected_version"), minVersion]);
return 1;
} else if ([javaHome hasPrefix:@(getenv("POJAV_HOME"))]) {
// Activate Library Validation bypass for external runtime
init_bypassDyldLibValidation();
// Symlink libawt_xawt.dylib
NSString *dest = [NSString stringWithFormat:@"%@/lib/libawt_xawt.dylib", javaHome];
NSString *source = [NSString stringWithFormat:@"%s/Frameworks/libawt_xawt.dylib", getenv("BUNDLE_PATH")];
NSError *error;
[fm createSymbolicLinkAtPath:dest withDestinationPath:source error:&error];
if (error) {
NSLog(@"[JavaLauncher] Symlink libawt_xawt.dylib failed: %@", error.localizedDescription);
}
}
setenv("JAVA_HOME", javaHome.UTF8String, 1);
NSLog(@"[JavaLauncher] JAVA_HOME has been set to %@", javaHome);
NSString *renderer = getPreference(@"renderer");
if (renderer.length == 0) {
renderer = @"auto";
setPreference(@"renderer", renderer);
NSLog(@"[JavaLauncher] RENDERER environment variable was not set. Defaulting to %@ for future use.\n", renderer);
} else {
NSLog(@"[JavaLauncher] Restored preference: RENDERER is set to %@\n", renderer);
}
setenv("POJAV_RENDERER", renderer.UTF8String, 1);
int allocmem;
if ([getPreference(@"auto_ram") boolValue]) {
CGFloat autoRatio = getEntitlementValue(@"com.apple.private.memorystatus") ? 0.4 : 0.25;
allocmem = roundf((NSProcessInfo.processInfo.physicalMemory / 1048576) * autoRatio);
} else {
allocmem = [getPreference(@"allocated_memory") intValue];
}
NSLog(@"[JavaLauncher] Max RAM allocation is set to %d MB", allocmem);
int margc = -1;
const char *margv[1000];
margv[++margc] = [NSString stringWithFormat:@"%@/bin/java", javaHome].UTF8String;
margv[++margc] = "-XstartOnFirstThread";
margv[++margc] = "-Djava.system.class.loader=net.kdt.pojavlaunch.PojavClassLoader";
margv[++margc] = "-Xms128M";
margv[++margc] = [NSString stringWithFormat:@"-Xmx%dM", allocmem].UTF8String;
margv[++margc] = [NSString stringWithFormat:@"-Djava.library.path=%1$s/Frameworks", getenv("BUNDLE_PATH")].UTF8String;
margv[++margc] = [NSString stringWithFormat:@"-Djna.boot.library.path=%s/Frameworks", getenv("BUNDLE_PATH")].UTF8String;
margv[++margc] = [NSString stringWithFormat:@"-Duser.dir=%s", getenv("POJAV_GAME_DIR")].UTF8String;
margv[++margc] = [NSString stringWithFormat:@"-Duser.home=%s", getenv("POJAV_HOME")].UTF8String;
margv[++margc] = [NSString stringWithFormat:@"-Duser.timezone=%@", NSTimeZone.localTimeZone.name].UTF8String;
margv[++margc] = [NSString stringWithFormat:@"-DUIScreen.maximumFramesPerSecond=%d", (int)UIScreen.mainScreen.maximumFramesPerSecond].UTF8String;
margv[++margc] = "-Dorg.lwjgl.glfw.checkThread0=false";
margv[++margc] = "-Dorg.lwjgl.system.allocator=system";
margv[++margc] = "-Dorg.lwjgl.util.NoChecks=true";
margv[++margc] = "-Dlog4j2.formatMsgNoLookups=true";
NSString *librariesPath = [NSString stringWithFormat:@"%s/libs", getenv("BUNDLE_PATH")];
if([getPreference(@"cosmetica") boolValue]) {
margv[++margc] = [NSString stringWithFormat:@"-javaagent:%@/arc_dns_injector.jar=23.95.137.176", librariesPath].UTF8String;
}
// Workaround random stack guard allocation crashes
margv[++margc] = "-XX:+UnlockExperimentalVMOptions";
margv[++margc] = "-XX:+DisablePrimordialThreadGuardPages";
// Setup Caciocavallo
margv[++margc] = "-Djava.awt.headless=false";
margv[++margc] = "-Dcacio.font.fontmanager=sun.awt.X11FontManager";
margv[++margc] = "-Dcacio.font.fontscaler=sun.font.FreetypeFontScaler";
// Disable Forge 1.16.x early progress window
margv[++margc] = "-Dfml.earlyprogresswindow=false";
// Load java
NSString *libjlipath8 = [NSString stringWithFormat:@"%@/lib/jli/libjli.dylib", javaHome]; // java 8
NSString *libjlipath11 = [NSString stringWithFormat:@"%@/lib/libjli.dylib", javaHome]; // java 11+
BOOL isJava8 = [fm fileExistsAtPath:libjlipath8];
setenv("INTERNAL_JLI_PATH", (isJava8 ? libjlipath8 : libjlipath11).UTF8String, 1);
void* libjli = dlopen(getenv("INTERNAL_JLI_PATH"), RTLD_GLOBAL);
if (!libjli) {
const char *error = dlerror();
NSLog(@"[Init] JLI lib = NULL: %s", error);
UIKit_returnToSplitView();
showDialog(localize(@"Error", nil), @(error));
return 1;
}
if (isJava8) {
// Setup Caciocavallo
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";
} else {
// Required by Cosmetica to inject DNS
margv[++margc] = "--add-opens=java.base/java.net=ALL-UNNAMED";
// Setup Caciocavallo
margv[++margc] = "-Dswing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel";
margv[++margc] = "-Dawt.toolkit=com.github.caciocavallosilano.cacio.ctc.CTCToolkit";
margv[++margc] = "-Djava.awt.graphicsenv=com.github.caciocavallosilano.cacio.ctc.CTCGraphicsEnvironment";
// Required by Caciocavallo17 to access internal API
margv[++margc] = "--add-exports=java.desktop/java.awt=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/java.awt.peer=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/sun.awt.image=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/sun.java2d=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/java.awt.dnd.peer=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/sun.awt=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/sun.awt.event=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/sun.awt.datatransfer=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.desktop/sun.font=ALL-UNNAMED";
margv[++margc] = "--add-exports=java.base/sun.security.action=ALL-UNNAMED";
margv[++margc] = "--add-opens=java.base/java.util=ALL-UNNAMED";
margv[++margc] = "--add-opens=java.desktop/java.awt=ALL-UNNAMED";
margv[++margc] = "--add-opens=java.desktop/sun.font=ALL-UNNAMED";
margv[++margc] = "--add-opens=java.desktop/sun.java2d=ALL-UNNAMED";
margv[++margc] = "--add-opens=java.base/java.lang.reflect=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";
}
// Add Caciocavallo bootclasspath
NSString *cacio_classpath = [NSString stringWithFormat:@"-Xbootclasspath/%s", isJava8 ? "p" : "a"];
NSString *cacio_libs_path = [NSString stringWithFormat:@"%s/libs_caciocavallo%s", getenv("BUNDLE_PATH"), isJava8 ? "" : "17"];
NSArray *files = [fm contentsOfDirectoryAtPath:cacio_libs_path error:nil];
for(NSString *file in files) {
if ([file hasSuffix:@".jar"]) {
cacio_classpath = [NSString stringWithFormat:@"%@:%@/%@", cacio_classpath, cacio_libs_path, file];
}
}
margv[++margc] = cacio_classpath.UTF8String;
if (UIDevice.currentDevice.systemVersion.floatValue < 14 ||
!getEntitlementValue(@"com.apple.developer.kernel.extended-virtual-addressing")) {
// In jailed environment, where extended virtual addressing entitlement isn't
// present (for free dev account), allocating compressed space fails.
// FIXME: does extended VA allow allocating compressed class space?
margv[++margc] = "-XX:-UseCompressedClassPointers";
}
if ([launchTarget isKindOfClass:NSDictionary.class]) {
for (NSString *arg in launchTarget[@"arguments"][@"jvm_processed"]) {
margv[++margc] = arg.UTF8String;
}
}
init_loadCustomJvmFlags(&margc, (const char **)margv);
NSLog(@"[Init] Found JLI lib");
margv[++margc] = "-cp";
margv[++margc] = [NSString stringWithFormat:@"%@/*", librariesPath].UTF8String;
margv[++margc] = "net.kdt.pojavlaunch.PojavLauncher";
if (username == nil) {
margv[++margc] = ".LaunchJAR";
} else {
margv[++margc] = username.UTF8String;
}
if ([launchTarget isKindOfClass:NSDictionary.class]) {
margv[++margc] = [launchTarget[@"id"] UTF8String];
} else {
margv[++margc] = [launchTarget UTF8String];
}
margv[++margc] = [NSString stringWithFormat:@"%dx%d", width, height].UTF8String;
pJLI_Launch = (JLI_Launch_func *)dlsym(libjli, "JLI_Launch");
if (NULL == pJLI_Launch) {
NSLog(@"[Init] JLI_Launch = NULL");
return -2;
}
NSLog(@"[Init] Calling JLI_Launch");
// Cr4shed known issue: exit after crash dump,
// reset signal handler so that JVM can catch them
signal(SIGSEGV, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGFPE, SIG_DFL);
// Free split VC
tmpRootVC = nil;
return pJLI_Launch(++margc, margv,
0, NULL, // sizeof(const_jargs) / sizeof(char *), const_jargs,
0, NULL, // sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
// These values are ignoree in Java 17, so keep it anyways
"1.8.0-internal",
"1.8",
"java", "openjdk",
/* (const_jargs != NULL) ? JNI_TRUE : */ JNI_FALSE,
JNI_TRUE, JNI_FALSE, JNI_TRUE);
}