Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ public final class LauncherHelper {
private final GameSettings.Effective setting;
private LauncherVisibility launcherVisibility;
private boolean showLogs;

/// The crash window that hosted the launch progress of a restart; closed once the restart
/// launch succeeds, kept open on failure for inspecting the logs.
private @Nullable GameCrashWindow restartHostWindow;
private QuickPlayOption quickPlayOption;
private boolean disableOfflineSkin = false;

Expand Down Expand Up @@ -149,6 +153,23 @@ public void makeLaunchScript(Path scriptFile) {
launch();
}

/// Restarts the crashed game with the launch context captured by this helper, e.g. the account
/// and the QuickPlay option. The launch progress is shown as an overlay in the hosting crash
/// window, which is closed when the launch succeeds and stays open on failure.
///
/// @param hostWindow the crash window that invoked this restart
private void restartCrashedGame(GameCrashWindow hostWindow) {
if (launcherVisibility == LauncherVisibility.HIDE) {
// The platform must outlive the crash window, which closes when the launch succeeds.
setImplicitExit(false);
}

restartHostWindow = Objects.requireNonNull(hostWindow);

LOG.info("Restarting game instance: " + gameInstance.getId());
launch0();
}

/// Builds and executes the launch pipeline for the captured game instance.
private void launch0() {
// https://github.com/HMCL-dev/HMCL/pull/4121
Expand Down Expand Up @@ -333,6 +354,13 @@ private void launch0() {
@Override
public void onStop(boolean success, TaskExecutor executor) {
runLater(() -> {
if (success && restartHostWindow != null) {
restartHostWindow.close();
restartHostWindow = null;
} else if (!success && launcherVisibility == LauncherVisibility.HIDE) {
setImplicitExit(true);
}

// Check if the application has stopped
// because onStop will be invoked if tasks fail when the executor service shut down.
if (!Controllers.isStopped()) {
Expand Down Expand Up @@ -951,6 +979,8 @@ private void finishLaunch() {
// If application was stopped and execution services did not finish termination,
// these codes will be executed.
if (Controllers.getStage() != null) {
// The platform must survive the main window being closed while the game runs.
setImplicitExit(false);
Controllers.getStage().close();
Controllers.shutdown();
Schedulers.shutdown();
Expand Down Expand Up @@ -1034,7 +1064,7 @@ public void onExit(int exitCode, ExitType exitType) {

if (exitType != ExitType.NORMAL) {
gameInstance.markLaunchedAbnormally();
runLater(() -> new GameCrashWindow(process, exitType, gameInstance, launchOptions, logs).show());
runLater(() -> new GameCrashWindow(process, exitType, gameInstance, launchOptions, logs, launchingStepsPane, LauncherHelper.this::restartCrashedGame).show());
}

checkExit();
Expand Down
28 changes: 25 additions & 3 deletions HMCL/src/main/java/org/jackhuang/hmcl/ui/GameCrashWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
Expand All @@ -40,6 +41,7 @@
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.theme.Themes;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
Expand All @@ -61,6 +63,7 @@
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand All @@ -87,14 +90,24 @@ public class GameCrashWindow extends Stage {

private final List<Log> logs;

public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, HMCLGameInstance gameInstance, LaunchOptions launchOptions, List<Log> logs) {
Themes.applyNativeDarkMode(this);
/// The action invoked when the user clicks the restart button, relaunching the crashed game
/// with the original launch context. It receives this crash window, which hosts the launch
/// progress overlay and is closed by the launcher when the launch succeeds.
private final Consumer<GameCrashWindow> restartAction;

/// The launch progress pane shown as an overlay in this window while the restarted game is
/// launching.
private final Region launchingPane;

public GameCrashWindow(ManagedProcess managedProcess, ProcessListener.ExitType exitType, HMCLGameInstance gameInstance, LaunchOptions launchOptions, List<Log> logs, Region launchingPane, Consumer<GameCrashWindow> restartAction) {
Themes.applyNativeDarkMode(this);
this.managedProcess = managedProcess;
this.exitType = exitType;
this.gameInstance = gameInstance;
this.launchOptions = launchOptions;
this.logs = logs;
this.launchingPane = Objects.requireNonNull(launchingPane);
this.restartAction = Objects.requireNonNull(restartAction);

memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + " " + i18n("settings.memory.unit.mib")).orElse("-");

Expand Down Expand Up @@ -467,7 +480,16 @@ private final class View extends VBox {
toolBar.setPadding(new Insets(8));
toolBar.setSpacing(8);
toolBar.getStyleClass().add("jfx-tool-bar");
toolBar.getChildren().setAll(exportButtonPane, logButton, helpButton);

JFXButton restartButton = FXUtils.newRaisedButton(i18n("game.crash.restart"));
launchingPane.addEventHandler(DialogCloseEvent.CLOSE, event -> restartButton.setDisable(false));
restartButton.setOnAction(e -> {
restartButton.setDisable(true);
DialogUtils.show(stackPane, launchingPane);
restartAction.accept(GameCrashWindow.this);
});

toolBar.getChildren().setAll(exportButtonPane, restartButton, logButton, helpButton);
}

getChildren().setAll(titlePane, infoPane, moddedPane, gameDirPane, toolBar);
Expand Down
1 change: 1 addition & 0 deletions HMCL/src/main/resources/assets/lang/I18N.properties
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,7 @@ game.crash.reason.unsatisfied_link_error=Failed to launch Minecraft because of m
If you have not, please check if you have missing dependency mods.\n\
\n\
Otherwise, if you believe this is caused by HMCL, please provide feedback to us.
game.crash.restart=Restart Game
game.crash.title=Game Crashed
game.directory=Game Path
game.version=Game Instance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ game.crash.reason.stacktrace=原因未知。请点击日志按钮查看详细信
game.crash.reason.too_old_java=当前游戏由于 Java 版本过低,无法继续运行。\n你需要在“(全局/实例特定) 游戏设置 → 游戏 Java”中更换 Java %1$s 或更新版本的 Java,并重新启动游戏。如果没有下载安装,你可以点击 <a href="https://learn.microsoft.com/java/openjdk/download">此处</a> 下载 Microsoft JDK。
game.crash.reason.unknown=原因未知。请点击日志按钮查看详细信息。
game.crash.reason.unsatisfied_link_error=当前游戏由于缺少本地库,无法继续运行。\n这些本地库缺失:%1$s。\n如果你在“(全局/实例特定) 游戏设置 → 高级设置”中修改了本地库路径选项,请你修改回默认模式。\n如果你正在使用默认模式,请检查游戏文件夹路径是否只包含英文字母、数字和下划线。\n如果是,那么请检查是否为模组或 HMCL 导致了本地库缺失的问题。如果你确定是 HMCL 引起的,建议你向我们反馈。\n<b>对于 Windows 用户,你还可以尝试在“控制面板 → 时钟和区域 → 区域 → 管理 → 更改系统区域设置”中将“当前系统区域设置”修改为“中文(简体,中国大陆)”,并关闭“Beta 版:使用 Unicode UTF-8 提供全球语言支持”选项;</b>\n<b>或将游戏文件夹路径中的所有非英文字符的名称 (例如中文、空格等) 修改为英文字符。</b>\n如果你确实需要自定义本地库路径,你需要保证其中包含缺失的本地库!
game.crash.restart=重启游戏
game.crash.title=游戏意外退出
game.directory=游戏文件夹路径
game.version=游戏实例
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ game.crash.reason.stacktrace=原因未知。請點擊日誌按鈕查看詳細訊
game.crash.reason.too_old_java=目前遊戲由於 Java 版本過低,無法繼續執行。\n你需要在「(全域/實例特定) 遊戲設定 → 遊戲 Java」中更換 %1$s 或更高版本的 Java,並重新啟動遊戲。如果沒有下載安裝,你可以點擊 <a href="https://learn.microsoft.com/java/openjdk/download">此處</a> 下載 Microsoft JDK。
game.crash.reason.unknown=原因未知。請點擊日誌按鈕查看詳細訊息。
game.crash.reason.unsatisfied_link_error=目前遊戲由於缺少本機庫,無法繼續執行。\n這些本機庫缺失:%1$s。\n如果你在「(全域/實例特定) 遊戲設定 → 進階設定」中修改了本機庫路徑選項,請你修改回預設模式。\n如果你正在使用預設模式,請檢查遊戲目錄路徑是否只包含英文字母、數字和底線。\n如果是,那麼請檢查是否為模組或 HMCL 引起的本機庫缺失的問題。如果你確定是 HMCL 引起的,建議你向我們回報。\n<b>對於 Windows 使用者,你還可以嘗試在「控制台 → 時鐘和區域 → 地區 → 系統管理 → 變更系統區域設定」中,關閉「Beta:使用 Unicode UTF-8 提供全球語言支援」選項;</b>\n<b>或將遊戲目錄路徑中的所有非英文字母的名稱 (例如中文、空格等) 修改為英文字母。</b>\n如果你確實需要自訂本機庫路徑,你需要保證其中包含缺失的本機庫!
game.crash.restart=重新啟動遊戲
game.crash.title=遊戲意外退出
game.directory=遊戲目錄路徑
game.version=遊戲實例
Expand Down
Loading