-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSelfRelaunch.java
More file actions
181 lines (155 loc) · 8.3 KB
/
SelfRelaunch.java
File metadata and controls
181 lines (155 loc) · 8.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
* SelfRelaunch.java - Detects jpackage install layout and relaunches the JVM
* with the correct -Djava.library.path and bundled runtime if needed.
*
* Problem it solves:
* jpackage's bloxbox.exe launcher does not pass -Djava.library.path so
* JavaFX native libs (.dll) are not found and the app silently does nothing.
*
* How it works:
* 1. Detect if we are running from a jpackage install by checking for the
* bundled runtime at <jar_dir>/../runtime/bin/java(w).exe
* 2. If yes and -Djava.library.path is not already set to a valid lib dir,
* relaunch via the bundled JRE with the correct flags and exit this process
* 3. If already relaunched (sentinel property is set) do nothing - prevents
* infinite relaunch loop
* 4. If not a jpackage install (dev/run.ps1 launch) do nothing
*
*/
public class SelfRelaunch {
// ===== SENTINEL FLAG =====
// Set on the relaunched process so we know not to relaunch again
// Checked first in relaunchIfNeeded() before any other logic runs
private static final String ALREADY_RELAUNCHED = "bloxbox.relaunched";
// ===== JPACKAGE LAYOUT MARKERS =====
// jpackage always puts the bundled JRE at <install>\runtime\bin\
// and the app files at <install>\app\
// We detect the jpackage layout by checking for runtime\bin\java(w).exe
private static final String BUNDLED_JAVA_WIN = "javaw.exe";
private static final String BUNDLED_JAVA_UNIX = "java";
// ===== NATIVE LIB SENTINEL FILE =====
// Checked to confirm the lib dir is valid before skipping relaunch
// prism_d3d.dll must be present for JavaFX D3D pipeline to initialise
private static final String NATIVE_SENTINEL_WIN = "prism_d3d.dll";
private static final String NATIVE_SENTINEL_UNIX = "libprism_es2.so";
/**
* Call this as the very first line of Start.main() before anything else.
* If a relaunch is needed this method does not return - it exits the JVM.
* If no relaunch is needed it returns immediately and main() continues.
*
* @param args the original args from main() - passed through unchanged
*/
public static void relaunchIfNeeded(String[] args) {
// ===== GUARD: already relaunched - do not loop =====
if (System.getProperty(ALREADY_RELAUNCHED) != null) {
System.out.println("[relaunch] Already relaunched - continuing normally");
return;
}
boolean isWindows = System.getProperty("os.name", "").toLowerCase().contains("win");
String sentinel = isWindows ? NATIVE_SENTINEL_WIN : NATIVE_SENTINEL_UNIX;
// ===== GUARD: library path already set AND points to a valid lib dir =====
// Do not skip just because the property exists - jpackage may set it to
// a path that does not actually contain the JavaFX native libs yet.
// We confirm by checking for the D3D/ES2 sentinel dll/so file.
String existingLibPath = System.getProperty("java.library.path", "");
for (String entry : existingLibPath.split(java.io.File.pathSeparator)) {
if (new java.io.File(entry, sentinel).exists()) {
System.out.println("[relaunch] Valid java.library.path found at: " + entry + " - skipping relaunch");
return;
}
}
// ===== LOCATE THE JAR WE ARE RUNNING FROM =====
// getCodeSource().getLocation() returns a URL - may contain %20 for spaces.
// new File(uri) is the only safe decode path on all platforms.
// Do NOT use url.getPath() - leaves %20 literals in path on Windows.
java.net.URL loc = SelfRelaunch.class.getProtectionDomain()
.getCodeSource().getLocation();
if (loc == null) {
System.err.println("[relaunch] Cannot determine jar location - skipping relaunch");
return;
}
java.io.File jarFile;
try {
// Convert URL to URI then to File - handles spaces and special chars
jarFile = new java.io.File(loc.toURI());
} catch (java.net.URISyntaxException e) {
// Fallback: manually decode %20 and common percent-encoded chars
// Covers edge cases where toURI() itself throws on malformed URLs
String decoded = loc.getPath()
.replace("%20", " ")
.replace("%28", "(")
.replace("%29", ")")
.replace("%5B", "[")
.replace("%5D", "]");
jarFile = new java.io.File(decoded);
}
System.out.println("[relaunch] Jar location: " + jarFile.getAbsolutePath());
// ===== DETECT JPACKAGE LAYOUT =====
// jpackage places the jar at: <install>\app\BloxBox-Java.jar
// bundled JRE lives at: <install>\runtime\bin\javaw.exe
// If this structure is not present we are in a dev checkout - skip
java.io.File appDir = jarFile.getParentFile(); // <install>\app\
java.io.File installDir = appDir .getParentFile(); // <install>\
java.io.File runtimeBin = new java.io.File(installDir,
"runtime" + java.io.File.separator + "bin");
java.io.File bundledJava = new java.io.File(runtimeBin,
isWindows ? BUNDLED_JAVA_WIN : BUNDLED_JAVA_UNIX);
if (!bundledJava.exists()) {
// Not a jpackage install - running from dev checkout via run.ps1
System.out.println("[relaunch] No bundled JRE at " + bundledJava + " - dev mode, skipping");
return;
}
// ===== LOCATE THE NATIVE LIB DIRECTORY =====
// jpackage stages JavaFX dlls into <install>\app\lib\
// This is what -Djava.library.path must point at for D3D/glass to load
java.io.File libDir = new java.io.File(appDir, "lib");
if (!libDir.exists()) {
System.err.println("[relaunch] lib dir missing at " + libDir + " - skipping relaunch");
return;
}
// ===== BUILD THE RELAUNCH COMMAND =====
// Forward all original args so --debug, --game-log-output etc survive
java.util.List<String> cmd = new java.util.ArrayList<>();
// Use the bundled JRE - no system JDK required after install
cmd.add(bundledJava.getAbsolutePath());
// Sentinel flag - tells the relaunched process not to relaunch again
cmd.add("-D" + ALREADY_RELAUNCHED + "=true");
// Point JavaFX native lib loader at the bundled dlls
// Without this prism_d3d.dll / glass.dll are not found -> QuantumRenderer crash
cmd.add("-Djava.library.path=" + libDir.getAbsolutePath());
// Allow native access - suppresses the restricted-method warning and
// will be required in a future JDK release
cmd.add("--enable-native-access=ALL-UNNAMED");
// Forward any JAVA_TOOL_OPTIONS set by jpackage (e.g. -Xmx512m)
// Split on whitespace - each token is a separate JVM arg
String javaToolOpts = System.getenv("JAVA_TOOL_OPTIONS");
if (javaToolOpts != null && !javaToolOpts.isBlank()) {
for (String opt : javaToolOpts.split("\\s+")) {
if (!opt.isBlank()) cmd.add(opt);
}
}
// Jar and original CLI args - must come after all JVM flags
cmd.add("-jar");
cmd.add(jarFile.getAbsolutePath());
// Forward all original args from main() unchanged
// Covers --debug, --game-log-output, and any future flags
for (String arg : args) cmd.add(arg);
System.out.println("[relaunch] Relaunching with bundled JRE:");
System.out.println("[relaunch] " + String.join(" ", cmd));
try {
// ===== LAUNCH CHILD PROCESS AND EXIT PARENT =====
// inheritIO pipes child stdout/stderr to any console window present
// so log output is still visible after the relaunch
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.inheritIO();
pb.start();
// Exit this incorrectly-launched process immediately
// The child process is now the running app
System.exit(0);
} catch (Exception e) {
// Relaunch failed - log and fall through so the app tries anyway
// Better a degraded launch than a silent exit
System.err.println("[relaunch] Relaunch failed: " + e.getMessage() + " - continuing anyway");
}
}
}