Windows 平台自动使用 javaw.exe 启动游戏 (#7009)

This commit is contained in:
Glavo
2026-10-19 20:21:15 +08:00
committed by GitHub
parent 15754119d5
commit 8b147bf20a
16 changed files with 98 additions and 18 deletions
@@ -565,7 +565,13 @@ public class HMCLGameInstance extends DefaultGameInstance {
return getLayout().getInstanceGameSettingsFile(id);
}
public LaunchOptions.Builder getLaunchOptions(JavaRuntime javaVersion, Path gameDir, List<String> javaAgents, List<String> javaArguments, boolean makeLaunchScript) {
public LaunchOptions.Builder getLaunchOptions(
JavaRuntime javaRuntime,
Path javaExecutable,
Path gameDir,
List<String> javaAgents,
List<String> javaArguments,
boolean makeLaunchScript) {
GameSettings.Effective vs = getEffectiveSettings();
boolean noJVMOptions = vs.getInheritable(GameSettings::noJVMOptionsProperty);
boolean autoMemory = vs.getInheritable(GameSettings::autoMemoryProperty);
@@ -577,7 +583,7 @@ public class HMCLGameInstance extends DefaultGameInstance {
? null
: Math.toIntExact(HMCLGameRepository.getAutoAllocatedMemory(
SystemInfo.getPhysicalMemoryStatus().available(),
javaVersion.getPlatform()) / 1024L / 1024L);
javaRuntime.getPlatform()) / 1024L / 1024L);
} else {
maxMemory = vs.getMaxMemory();
}
@@ -585,7 +591,8 @@ public class HMCLGameInstance extends DefaultGameInstance {
LaunchOptions.Builder builder = new LaunchOptions.Builder()
.setInstanceId(getId())
.setGameDir(gameDir)
.setJava(javaVersion)
.setJava(javaRuntime)
.setJavaExecutable(javaExecutable)
.setVersionType(Metadata.TITLE)
.setVersionName(getId().id())
.setProfileName(Metadata.TITLE)
@@ -57,11 +57,11 @@ import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.SocketTimeoutException;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@@ -148,29 +148,46 @@ public final class LauncherHelper {
}
/// Builds and executes the launch pipeline for the captured game instance.
@SuppressWarnings("NonAtomicOperationOnVolatileField")
private void launch0() {
// https://github.com/HMCL-dev/HMCL/pull/4121
PROCESSES.removeIf(it -> it.get() == null);
final var context = new Object() {
volatile GameInstanceManifest launchManifest;
volatile JavaRuntime javaRuntime;
volatile Path javaExecutable;
};
HMCLGameRepository repository = repository();
DefaultDependencyManager dependencyManager = repository.getDependency();
// Resolve already deduplicated libraries; apply loader-specific argument repairs for this launch.
var launchManifest = new AtomicReference<>(LaunchManifestNormalizer.repairForLaunch(gameInstance.getResolvedManifest()));
boolean integrityCheck = gameInstance.unmarkLaunchedAbnormally();
CountDownLatch launchingLatch = new CountDownLatch(1);
List<String> javaAgents = new ArrayList<>(0);
List<String> javaArguments = new ArrayList<>(0);
AtomicReference<JavaRuntime> javaVersionRef = new AtomicReference<>();
// Resolve already deduplicated libraries; apply loader-specific argument repairs for this launch.
context.launchManifest = LaunchManifestNormalizer.repairForLaunch(gameInstance.getResolvedManifest());
TaskExecutor executor = checkGameState(gameInstance, setting, launchManifest.get())
TaskExecutor executor = checkGameState(gameInstance, setting, context.launchManifest)
.thenComposeAsync(java -> {
javaVersionRef.set(Objects.requireNonNull(java));
launchManifest.set(NativePatcher.patchNative(gameInstance, launchManifest.get(), java, setting, javaArguments));
context.javaRuntime = Objects.requireNonNull(java);
context.javaExecutable = java.getBinary();
if (java.getPlatform().os() == OperatingSystem.WINDOWS
&& scriptFile == null
&& !setting.getInheritable(GameSettings::notUseJavawProperty)
&& FileUtils.getName(java.getBinary()).equalsIgnoreCase("java.exe")) {
Path javawBinary = java.getBinary().resolveSibling("javaw.exe");
if (Files.isRegularFile(javawBinary)) {
context.javaExecutable = javawBinary;
}
}
context.launchManifest = NativePatcher.patchNative(gameInstance, context.launchManifest, java, setting, javaArguments);
if (setting.getInheritable(GameSettings::notCheckGameProperty))
return null;
return Task.allOf(
dependencyManager.checkGameCompletionAsync(gameInstance, launchManifest.get(), integrityCheck),
dependencyManager.checkGameCompletionAsync(gameInstance, context.launchManifest, integrityCheck),
Task.composeAsync(() -> {
try {
@Nullable ModpackConfiguration<?> configuration =
@@ -203,7 +220,7 @@ public final class LauncherHelper {
String agent = FileUtils.getAbsolutePath(file) + "=" + renderer.mesaDriverName();
if (GameLibrariesTask.shouldDownloadLibrary(repository, launchManifest.get(), lib, integrityCheck)) {
if (GameLibrariesTask.shouldDownloadLibrary(repository, context.launchManifest, lib, integrityCheck)) {
return new LibraryDownloadTask(dependencyManager, file, lib)
.thenRunAsync(() -> javaAgents.add(agent));
} else {
@@ -213,13 +230,13 @@ public final class LauncherHelper {
})
);
}).withStage("launch.state.dependencies")
.thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion(), launchManifest.get()))
.thenComposeAsync(() -> new GameVerificationFixTask(gameInstance, gameInstance.getVersion(), context.launchManifest))
.thenComposeAsync(() -> {
if (setting.getInheritable(GameSettings::allowAutoAgentProperty)
|| setting.getInheritable(GameSettings::noJVMOptionsProperty)
|| setting.getInheritable(GameSettings::noOptimizingJVMOptionsProperty)
|| Boolean.TRUE.equals(state().getShownTips().get(LWJGL_3_4_1_TIP))
|| !NativePatcher.needPatchMemoryUtil(launchManifest.get(), javaVersionRef.get().getParsedVersion())) {
|| !NativePatcher.needPatchMemoryUtil(context.launchManifest, context.javaRuntime.getParsedVersion())) {
return Task.completed(null);
} else {
CompletableFuture<Void> future = new CompletableFuture<>();
@@ -239,7 +256,12 @@ public final class LauncherHelper {
.thenComposeAsync(() -> logIn(account).withStage("launch.state.logging_in"))
.thenComposeAsync(authInfo -> Task.supplyAsync(() -> {
LaunchOptions.Builder launchOptionsBuilder = gameInstance.getLaunchOptions(
javaVersionRef.get(), repository.getBaseDirectory(), javaAgents, javaArguments, scriptFile != null);
context.javaRuntime,
context.javaExecutable,
repository.getBaseDirectory(),
javaAgents,
javaArguments,
scriptFile != null);
if (disableOfflineSkin) {
launchOptionsBuilder.setDaemon(false);
}
@@ -248,7 +270,7 @@ public final class LauncherHelper {
}
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS && setting.getInheritable(GameSettings::highPerformanceProperty)) {
@Nullable String javaPathGpuReg = FileUtils.getAbsolutePath(javaVersionRef.get().getBinary());
@Nullable String javaPathGpuReg = FileUtils.getAbsolutePath(context.javaExecutable);
try {
WinReg reg = WinReg.INSTANCE;
if (reg != null) {
@@ -283,7 +305,7 @@ public final class LauncherHelper {
return new HMCLGameLauncher(
gameInstance,
launchManifest.get(),
context.launchManifest,
authInfo,
launchOptions,
launcherVisibility == LauncherVisibility.CLOSE
@@ -388,6 +388,19 @@ public sealed abstract class GameSettings extends ObservableSetting {
return detectedJava;
}
/// Property name for disabling automatic replacement with `javaw.exe` on Windows.
public static final String PROPERTY_NOT_USE_JAVAW = "notUseJavaw";
/// Whether to keep the selected Java executable instead of replacing it with `javaw.exe`; defaults to `false`.
@SerializedName(PROPERTY_NOT_USE_JAVAW)
private final InheritableProperty<Boolean> notUseJavaw = newInheritableProperty(PROPERTY_NOT_USE_JAVAW, false);
/// Returns the property disabling automatic replacement of a Windows Java executable with a sibling `javaw.exe` file.
/// If `true`, the selected executable is used unchanged. This property has no effect on other runtime platforms.
public InheritableProperty<Boolean> notUseJavawProperty() {
return notUseJavaw;
}
/// Property name for customized JVM options.
public static final String PROPERTY_JVM_OPTIONS = "jvmOptions";
@@ -643,6 +643,11 @@ public final class GameSettingsPage<S extends GameSettings> extends StackPane
jvmSettings
);
{
var notUseJavawPane = createInheritableBooleanButton(GameSettings::notUseJavawProperty);
jvmSettings.getContent().add(notUseJavawPane);
notUseJavawPane.setTitle(i18n("settings.advanced.not_use_javaw"));
notUseJavawPane.setSubtitle(i18n("settings.advanced.windows_only"));
var noJVMArgsPane = createInheritableBooleanButton(GameSettings::noJVMOptionsProperty);
jvmSettings.getContent().add(noJVMArgsPane);
noJVMArgsPane.setTitle(i18n("settings.advanced.no_jvm_args"));
@@ -1433,6 +1433,7 @@ settings.advanced.natives_directory=Native Library Path
settings.advanced.natives_directory.custom.enabled=Use Custom Native Libraries
settings.advanced.no_jvm_args=Do not add default JVM arguments
settings.advanced.no_optimizing_jvm_args=Do not add default JVM optimization arguments
settings.advanced.not_use_javaw=Do not use javaw.exe
settings.advanced.precall_command=Pre-launch Command
settings.advanced.precall_command.prompt=Commands to execute before the game launches
settings.advanced.process_priority=Process Priority
@@ -176,6 +176,7 @@ addon.check_update.empty=جميع المودات محدّثة
addon.check_update.failed_check=فشل التحقق من التحديثات.
addon.check_update.failed_download=فشل تنزيل بعض الملفات.
addon.check_update.file=الملف
# addon.check_update.only_preview=
# addon.check_update.preview=
addon.check_update.source=المصدر
addon.check_update.target_version=الإصدار المستهدف
@@ -1432,6 +1433,7 @@ settings.advanced.natives_directory=مسار المكتبات الأصلية
# settings.advanced.natives_directory.custom.enabled=
settings.advanced.no_jvm_args=عدم إضافة وسيطات JVM الافتراضية
settings.advanced.no_optimizing_jvm_args=عدم إضافة وسيطات تحسين JVM الافتراضية
# settings.advanced.not_use_javaw=
settings.advanced.precall_command=أمر ما قبل التشغيل
settings.advanced.precall_command.prompt=أوامر تُنفَّذ قبل تشغيل اللعبة
settings.advanced.process_priority=أولوية العملية
@@ -174,6 +174,7 @@ addon.check_update.empty=Alle Dateien sind auf dem neuesten Stand
addon.check_update.failed_check=Fehler beim Suchen nach Aktualisierungen.
addon.check_update.failed_download=Fehler beim Herunterladen einiger Dateien.
addon.check_update.file=Datei
# addon.check_update.only_preview=
# addon.check_update.preview=
addon.check_update.source=Quelle
addon.check_update.target_version=Zielversion
@@ -1431,6 +1432,7 @@ settings.advanced.natives_directory=Pfad der nativen Bibliotheken
settings.advanced.natives_directory.custom.enabled=Benutzerdefinierte native Bibliotheken verwenden
settings.advanced.no_jvm_args=Standard-JVM-Argumente nicht hinzufügen
settings.advanced.no_optimizing_jvm_args=Standard-JVM-Optimierungsargumente nicht hinzufügen
# settings.advanced.not_use_javaw=
settings.advanced.precall_command=Pre-Launch-Befehl
settings.advanced.precall_command.prompt=Befehle, die vor dem Spielstart ausgeführt werden sollen
settings.advanced.process_priority=Prozesspriorität
@@ -174,6 +174,7 @@ addon.check_update.empty=Todos los mods están actualizados
addon.check_update.failed_check=No se ha podido comprobar si hay actualizaciones.
addon.check_update.failed_download=No se han podido descargar algunos de los archivos.
addon.check_update.file=Archivo
# addon.check_update.only_preview=
# addon.check_update.preview=
addon.check_update.source=Fuente
addon.check_update.target_version=Versión de destino
@@ -1424,6 +1425,7 @@ settings.advanced.natives_directory=Ruta de la biblioteca nativa
# settings.advanced.natives_directory.custom.enabled=
settings.advanced.no_jvm_args=No añadir argumentos JVM por defecto
settings.advanced.no_optimizing_jvm_args=No añadir argumentos de optimización de JVM
# settings.advanced.not_use_javaw=
settings.advanced.precall_command=Comando pre-lanzamiento:
settings.advanced.precall_command.prompt=El comando se ejecuta antes de que los juegos se lance
settings.advanced.process_priority=Prioridad del proceso
@@ -163,6 +163,7 @@ addon.check_update.current_version=Current
addon.check_update.failed_check=更新のチェックに失敗しました
addon.check_update.failed_download=一部のファイルのダウンロードに失敗しました
addon.check_update.file=ファイル
# addon.check_update.only_preview=
# addon.check_update.preview=
addon.check_update.source=Source
addon.check_update.target_version=Target
@@ -1228,6 +1229,7 @@ settings.advanced.natives_directory=ネイティブライブラリパス(例
# settings.advanced.natives_directory.custom.enabled=
settings.advanced.no_jvm_args=デフォルトのJVM引数なし
# settings.advanced.no_optimizing_jvm_args=
# settings.advanced.not_use_javaw=
settings.advanced.precall_command=起動前コマンド
settings.advanced.precall_command.prompt=ゲーム開始前に実行されます
settings.advanced.process_priority=プロセスの優先度
@@ -174,6 +174,7 @@ addon.check_update.empty=無改囊可迭更
addon.check_update.failed_check=檢囊迭更未成
addon.check_update.failed_download=有引案未成
addon.check_update.file=案
# addon.check_update.only_preview=
# addon.check_update.preview=
addon.check_update.source=源
addon.check_update.target_version=將至之版
@@ -1240,6 +1241,7 @@ settings.advanced.natives_directory=府庫之徑 (LWJGL)
# settings.advanced.natives_directory.custom.enabled=
settings.advanced.no_jvm_args=無添爪哇虛機之本通弦
# settings.advanced.no_optimizing_jvm_args=
# settings.advanced.not_use_javaw=
settings.advanced.precall_command=令於戲前
settings.advanced.precall_command.prompt=將前戲啟而行
settings.advanced.process_priority=進程之次
@@ -170,6 +170,7 @@ addon.check_update.empty=Все файлы актуальны
addon.check_update.failed_check=Не удалось проверить обновления.
addon.check_update.failed_download=Не удалось скачать некоторые файлы.
addon.check_update.file=Файл
# addon.check_update.only_preview=
# addon.check_update.preview=
addon.check_update.source=Источник
addon.check_update.target_version=Целевая версия
@@ -1364,6 +1365,7 @@ settings.advanced.natives_directory=Папка с нативными библи
settings.advanced.natives_directory.custom.enabled=Использовать свои нативные библиотеки
settings.advanced.no_jvm_args=Не добавлять аргументы JVM по умолчанию
settings.advanced.no_optimizing_jvm_args=Не добавлять аргументы оптимизации JVM по умолчанию
# settings.advanced.not_use_javaw=
settings.advanced.precall_command=Команда перед запуском
settings.advanced.precall_command.prompt=Команды, которые необходимо выполнить перед запуском игры
settings.advanced.process_priority=Приоритет процесса
@@ -176,6 +176,7 @@ addon.check_update.empty=Усі моди оновлені
addon.check_update.failed_check=Не вдалося перевірити оновлення.
addon.check_update.failed_download=Не вдалося завантажити деякі файли.
addon.check_update.file=Файл
# addon.check_update.only_preview=
# addon.check_update.preview=
addon.check_update.source=Джерело
addon.check_update.target_version=Цільова версія
@@ -1387,6 +1388,7 @@ settings.advanced.natives_directory=Шлях до нативних бібліо
settings.advanced.natives_directory.custom.enabled=Використовувати власні нативні бібліотеки
settings.advanced.no_jvm_args=Не додавати типові аргументи JVM
settings.advanced.no_optimizing_jvm_args=Не додавати типові аргументи оптимізації JVM
# settings.advanced.not_use_javaw=
settings.advanced.precall_command=Команда перед запуском
settings.advanced.precall_command.prompt=Команди для виконання перед запуском гри
settings.advanced.process_priority=Пріоритет процесу
@@ -1245,6 +1245,7 @@ settings.advanced.natives_directory=本地库路径 (LWJGL)
settings.advanced.natives_directory.custom.enabled=使用自定义本地库
settings.advanced.no_jvm_args=不添加默认的 Java 虚拟机参数
settings.advanced.no_optimizing_jvm_args=不自动添加 Java 虚拟机优化参数
settings.advanced.not_use_javaw=不使用 javaw.exe
settings.advanced.precall_command=游戏启动前执行命令
settings.advanced.precall_command.prompt=将在游戏启动前调用
settings.advanced.process_priority=进程优先级
@@ -1239,6 +1239,7 @@ settings.advanced.natives_directory=本機庫路徑 (LWJGL)
settings.advanced.natives_directory.custom.enabled=使用自訂本機庫
settings.advanced.no_jvm_args=不新增預設的 Java 虛擬機參數
settings.advanced.no_optimizing_jvm_args=不自動新增 Java 虛擬機最佳化參數
settings.advanced.not_use_javaw=不使用 javaw.exe
settings.advanced.precall_command=遊戲啟動前執行指令
settings.advanced.precall_command.prompt=將在遊戲啟動前呼叫使用
settings.advanced.process_priority=處理程式優先度
@@ -33,6 +33,7 @@ public class LaunchOptions implements Serializable {
private GameInstanceID instanceId;
private Path gameDir;
private JavaRuntime java;
private Path javaExecutable;
private String versionName;
private String versionType;
private String profileName;
@@ -85,6 +86,16 @@ public class LaunchOptions implements Serializable {
return java;
}
/// Returns the executable selected for launch, falling back to [JavaRuntime#getBinary()] if none is specified.
/// The path is used without automatic executable substitution or file validation.
public Path getJavaExecutable() {
if (javaExecutable != null)
return javaExecutable;
if (java != null)
return java.getBinary();
return null;
}
/**
* Will shown in the left bottom corner of the main menu of Minecraft.
* null if use the id of launch version.
@@ -336,6 +347,11 @@ public class LaunchOptions implements Serializable {
return this;
}
public Builder setJavaExecutable(Path javaExecutable) {
options.javaExecutable = javaExecutable;
return this;
}
public Builder setVersionName(String versionName) {
options.versionName = versionName;
return this;
@@ -98,7 +98,7 @@ public class DefaultLauncher extends Launcher {
if (StringUtils.isNotBlank(options.getWrapper()))
res.addAllWithoutParsing(StringUtils.tokenize(options.getWrapper(), getEnvVars(nativeFolder)));
res.add(options.getJava().getBinary().toString());
res.add(options.getJavaExecutable().toString());
res.addAllWithoutParsingAndReadExternal(options.getOverrideJavaArguments());