Launching messages

This commit is contained in:
huangyuhui 2018-02-02 13:44:53 +08:00
parent 0a4944f120
commit f144dd888d
12 changed files with 295 additions and 65 deletions

View File

@ -24,8 +24,7 @@ import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.launch.DefaultLauncher;
import org.jackhuang.hmcl.launch.ProcessListener;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
@ -34,6 +33,7 @@ import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.*;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.ui.LogWindow;
import org.jackhuang.hmcl.ui.construct.MessageBox;
@ -62,10 +62,12 @@ public final class LauncherHelper {
Version version = repository.getVersion(selectedVersion);
VersionSetting setting = profile.getVersionSetting(selectedVersion);
try {
checkGameState(profile, setting, version, () -> launch0(selectedVersion, scriptFile));
} catch (InterruptedException ignore) {
}
Platform.runLater(() -> {
try {
checkGameState(profile, setting, version, () -> Schedulers.newThread().schedule(() -> launch0(selectedVersion, scriptFile)));
} catch (InterruptedException ignore) {
}
});
}
private void launch0(String selectedVersion, File scriptFile) {
@ -97,20 +99,31 @@ public final class LauncherHelper {
));
}))
.then(variables -> {
DefaultLauncher launcher = variables.<DefaultLauncher>get("launcher");
if (scriptFile == null) {
return variables.<DefaultLauncher>get("launcher").launchAsync().setName(Main.i18n("version.launch"));
return new LaunchTask<>(launcher::launch).setName(Main.i18n("version.launch"));
} else {
return variables.<DefaultLauncher>get("launcher").makeLaunchScriptAsync(scriptFile).setName(Main.i18n("version.launch"));
return new LaunchTask<>(() -> {
launcher.makeLaunchScript(scriptFile);
return null;
}).setName(Main.i18n("version.launch_script"));
}
})
.then(Task.of(variables -> {
if (scriptFile == null) {
PROCESSES.add(variables.get(DefaultLauncher.LAUNCH_ASYNC_ID));
ManagedProcess process = variables.get(LaunchTask.LAUNCH_ID);
PROCESSES.add(process);
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
Main.stopApplication();
else
launchingStepsPane.setCancel(() -> {
process.stop();
Controllers.closeDialog();
});
} else
Platform.runLater(() ->
Controllers.dialog(Main.i18n("version.launch_script.success", scriptFile.getAbsolutePath())));
}))
.executor();
@ -129,7 +142,9 @@ public final class LauncherHelper {
@Override
public void onTerminate() {
Platform.runLater(() -> {
Controllers.dialog(StringUtils.getStackTrace(executor.getLastException()), Main.i18n("launch.failed"), MessageBox.ERROR_MESSAGE, Controllers::closeDialog);
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
scriptFile == null ? Main.i18n("launch.failed") : Main.i18n("version.launch_script.failed"),
MessageBox.ERROR_MESSAGE, Controllers::closeDialog);
});
}
});
@ -138,7 +153,7 @@ public final class LauncherHelper {
}
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
boolean flag = false;
boolean flag = false, suggest = true;
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)));
JavaVersion java = setting.getJavaVersion();
@ -156,6 +171,7 @@ public final class LauncherHelper {
if (java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
Controllers.dialog(Main.i18n("launch.advice.java9"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
suggest = false;
flag = true;
}
@ -174,7 +190,10 @@ public final class LauncherHelper {
flag = true;
}
if (!flag)
if (flag) {
if (suggest && Controllers.getDialogContent() instanceof MessageDialogPane)
((MessageDialogPane) Controllers.getDialogContent()).disableClosingDialog();
} else
onAccept.run();
}
@ -215,6 +234,34 @@ public final class LauncherHelper {
}
}
class LaunchTask<T> extends TaskResult<T> {
private final ExceptionalSupplier<T, ?> supplier;
public LaunchTask(ExceptionalSupplier<T, ?> supplier) {
this.supplier = supplier;
}
@Override
public void execute() throws Exception {
try {
setResult(supplier.get());
} catch (PermissionException e) {
throw new I18nException(Main.i18n("launch.failed.executable_permission"), e);
} catch (ProcessCreationException e) {
throw new I18nException(Main.i18n("launch.failed.creating_process") + e.getLocalizedMessage(), e);
} catch (NotDecompressingNativesException e) {
throw new I18nException(Main.i18n("launch.failed.decompressing_natives") + e.getLocalizedMessage(), e);
}
}
@Override
public String getId() {
return LAUNCH_ID;
}
static final String LAUNCH_ID = "launch";
}
/**
* The managed process listener.
* Guarantee that one [JavaProcess], one [HMCLProcessListener].
@ -302,9 +349,22 @@ public final class LauncherHelper {
@Override
public void onExit(int exitCode, ExitType exitType) {
if (exitType == ExitType.INTERRUPTED)
return;
if (exitType != ExitType.NORMAL && logWindow == null)
Platform.runLater(() -> {
logWindow = new LogWindow();
switch (exitType) {
case JVM_ERROR:
logWindow.setTitle(Main.i18n("launch.failed.cannot_create_jvm"));
break;
case APPLICATION_ERROR:
logWindow.setTitle(Main.i18n("launch.failed.exited_abnormally"));
break;
}
logWindow.show();
logWindow.onDone.register(() -> {
for (Map.Entry<String, Log4jLevel> entry : logs)

View File

@ -33,6 +33,8 @@ public final class MessageDialogPane extends StackPane {
private final String text;
private final JFXDialog dialog;
private boolean closingDialog = true;
@FXML
private JFXButton acceptButton;
@FXML
@ -57,7 +59,8 @@ public final class MessageDialogPane extends StackPane {
content.setText(text);
acceptButton.setOnMouseClicked(e -> {
dialog.close();
if (closingDialog)
dialog.close();
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
});
@ -84,7 +87,8 @@ public final class MessageDialogPane extends StackPane {
cancelButton.setVisible(true);
cancelButton.setOnMouseClicked(e -> {
dialog.close();
if (closingDialog)
dialog.close();
Optional.ofNullable(onCancel).ifPresent(Runnable::run);
});
@ -95,4 +99,8 @@ public final class MessageDialogPane extends StackPane {
graphic.setGraphic(SVG.help_circle("black", 40, 40));
}
public void disableClosingDialog() {
closingDialog = false;
}
}

View File

@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXProgressBar;
import javafx.beans.property.StringProperty;
@ -30,6 +31,7 @@ import java.util.Optional;
public class TaskExecutorDialogPane extends StackPane {
private TaskExecutor executor;
private Runnable onCancel;
@FXML
private JFXProgressBar progressBar;
@ -48,12 +50,11 @@ public class TaskExecutorDialogPane extends StackPane {
FXUtils.limitHeight(this, 200);
FXUtils.limitWidth(this, 400);
if (cancel == null)
btnCancel.setDisable(true);
setCancel(cancel);
btnCancel.setOnMouseClicked(e -> {
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
cancel.run();
onCancel.run();
});
}
@ -92,4 +93,10 @@ public class TaskExecutorDialogPane extends StackPane {
else
progressBar.setProgress(progress);
}
public void setCancel(Runnable onCancel) {
this.onCancel = onCancel;
JFXUtilities.runInFX(() -> btnCancel.setDisable(onCancel == null));
}
}

View File

@ -0,0 +1,44 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.util;
public class I18nException extends Exception {
private final String localizedMessage;
public I18nException(String localizedMessage) {
this.localizedMessage = localizedMessage;
}
public I18nException(String localizedMessage, Throwable suppressed) {
addSuppressed(suppressed);
this.localizedMessage = localizedMessage;
}
@Override
public String getLocalizedMessage() {
return localizedMessage;
}
public static String getStackTrace(Throwable e) {
if (e instanceof I18nException)
return e.getLocalizedMessage();
else
return StringUtils.getStackTrace(e);
}
}

View File

@ -145,13 +145,13 @@ launch.advice.not_enough_space=You have allocated too much memory, because the p
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game probably crash. The maximum memory is 1024MB. The launcher will try to launch it.
launch.circular_dependency_versions=Found circular dependency versions, please check if your client has been modified.
launch.failed=Unable to launch
launch.failed.cannot_create_jvm=We find that it cannot create java virutal machine. The Java argements may have problems. You can enable the no args mode in the settings.
launch.failed.decompressing_natives=Did not finish decompressing native libraries, continue launching game?
launch.failed.cannot_create_jvm=Java virtual machine cannot be created. Java arguments may have problems. You can enable the no args mode in the settings.
launch.failed.creating_process=Failed to create process, maybe your java path is wrong, please modify your java path.
launch.failed.decompressing_natives=Unable to decompress native libraries.
launch.failed.downloading_libraries=Did not finish downloading libraries, continue launching game?
launch.failed.executable_permission=Unable to add permission to the launch script
launch.failed.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
launch.failed.packing_jar=Failed to pack the jar.
launch.failed.sh_permission=Failed to add permission to the launch script.
launch.failed_creating_process=Failed to create process, maybe your java path is wrong, please modify your java path.
launch.failed.prelaunch_command=Prelaunch command fail.
launch.state.dependencies=Decompressing natives
launch.state.done=Done
launch.state.logging_in=Logging In
@ -366,8 +366,8 @@ version.game.old_beta=Beta
version.game.release=Release
version.game.snapshot=Snapshot
version.launch=Play
version.launch_script=Make Launching Script.
version.launch_script.failed=Failed to make script.
version.launch_script=Make Launching Script
version.launch_script.failed=Unable to make launch script.
version.launch_script.save=Save the launch script
version.launch_script.success=Finished script creation, %s.
version.manage.redownload_assets_index=Redownload Assets Index

View File

@ -146,12 +146,12 @@ launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于
launch.circular_dependency_versions=发现游戏版本循环引用,请确认您的客户端未被修改或修改导致出现此问题。
launch.failed=启动失败
launch.failed.cannot_create_jvm=截获到无法创建Java虚拟机可能是Java参数有问题可以在设置中开启无参数模式启动
launch.failed.decompressing_natives=未能解压游戏本地库,还要继续启动游戏吗?
launch.failed.creating_process=启动失败在创建新进程时发生错误可能是Java路径错误。
launch.failed.decompressing_natives=未能解压游戏本地库。
launch.failed.downloading_libraries=未完成游戏依赖库的下载,还要继续启动游戏吗?
launch.failed.executable_permission=未能为启动文件添加执行权限
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
launch.failed.packing_jar=在打包jar时发生错误
launch.failed.sh_permission=为启动文件添加权限时发生错误
launch.failed_creating_process=启动失败在创建新进程时发生错误可能是Java路径错误。
launch.failed.prelaunch_command=启动前执行命令执行失败。
launch.state.dependencies=正在处理游戏依赖
launch.state.done=启动完成
launch.state.logging_in=登录中

View File

@ -19,9 +19,6 @@ package org.jackhuang.hmcl.launch;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.task.SimpleTaskResult;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.*;
import java.io.*;
@ -220,14 +217,18 @@ public class DefaultLauncher extends Launcher {
protected void appendJvmArgs(List<String> result) {
}
public void decompressNatives(File destination) throws IOException {
for (Library library : version.getLibraries())
if (library.isNative())
CompressingUtils.unzip(repository.getLibraryFile(version, library),
destination,
"",
library.getExtract()::shouldExtract,
false);
public void decompressNatives(File destination) throws NotDecompressingNativesException {
try {
for (Library library : version.getLibraries())
if (library.isNative())
CompressingUtils.unzip(repository.getLibraryFile(version, library),
destination,
"",
library.getExtract()::shouldExtract,
false);
} catch (IOException e) {
throw new NotDecompressingNativesException(e);
}
}
protected Map<String, String> getConfigurations() {
@ -249,19 +250,26 @@ public class DefaultLauncher extends Launcher {
@Override
public ManagedProcess launch() throws IOException, InterruptedException {
File nativeFolder = Files.createTempDirectory("minecraft").toFile();
List<String> rawCommandLine = generateCommandLine(nativeFolder);
// To guarantee that when failed to generate code, we will not call precalled command
ProcessBuilder builder = new ProcessBuilder(rawCommandLine);
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
List<String> rawCommandLine = generateCommandLine(nativeFolder);
decompressNatives(nativeFolder);
if (StringUtils.isNotBlank(options.getPrecalledCommand()))
Runtime.getRuntime().exec(options.getPrecalledCommand()).waitFor();
builder.directory(repository.getRunDirectory(version.getId()))
.environment().put("APPDATA", options.getGameDir().getAbsoluteFile().getParent());
ManagedProcess p = new ManagedProcess(builder.start(), rawCommandLine);
Process process;
try {
ProcessBuilder builder = new ProcessBuilder(rawCommandLine);
builder.directory(repository.getRunDirectory(version.getId()))
.environment().put("APPDATA", options.getGameDir().getAbsoluteFile().getParent());
process = builder.start();
} catch (IOException e) {
throw new ProcessCreationException(e);
}
ManagedProcess p = new ManagedProcess(process, rawCommandLine);
if (listener == null)
startMonitors(p);
else
@ -269,10 +277,6 @@ public class DefaultLauncher extends Launcher {
return p;
}
public final TaskResult<ManagedProcess> launchAsync() {
return new SimpleTaskResult<>(LAUNCH_ASYNC_ID, this::launch);
}
@Override
public void makeLaunchScript(File scriptFile) throws IOException {
boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS;
@ -303,11 +307,7 @@ public class DefaultLauncher extends Launcher {
writer.write(StringUtils.makeCommand(generateCommandLine(nativeFolder)));
}
if (!scriptFile.setExecutable(true))
throw new IOException("Cannot make script file '" + scriptFile + "' executable.");
}
public final Task makeLaunchScriptAsync(File file) {
return Task.of(() -> makeLaunchScript(file));
throw new PermissionException();
}
private void startMonitors(ManagedProcess managedProcess) {
@ -362,7 +362,4 @@ public class DefaultLauncher extends Launcher {
managedProcess.addRelatedThread(stderr);
managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> processListener.onExit(exitCode, exitType)), "exit-waiter", isDaemon));
}
public static final String LAUNCH_ASYNC_ID = "process";
public static final String LAUNCH_SCRIPT_ASYNC_ID = "script";
}

View File

@ -65,16 +65,15 @@ final class ExitWaiter implements Runnable {
ProcessListener.ExitType exitType;
// LaunchWrapper will catch the exception logged and will exit normally.
if (exitCode != 0 || StringUtils.containsOne(errorLines, "Unable to launch")) {
EventBus.EVENT_BUS.fireEvent(new ProcessExitedAbnormallyEvent(this, process));
exitType = ProcessListener.ExitType.APPLICATION_ERROR;
} else if (exitCode != 0 && StringUtils.containsOne(errorLines,
if (exitCode != 0 && StringUtils.containsOne(errorLines,
"Could not create the Java Virtual Machine.",
"Error occurred during initialization of VM",
"A fatal exception has occurred. Program will exit.",
"Unable to launch")) {
"A fatal exception has occurred. Program will exit.")) {
EventBus.EVENT_BUS.fireEvent(new JVMLaunchFailedEvent(this, process));
exitType = ProcessListener.ExitType.JVM_ERROR;
} else if (exitCode != 0 || StringUtils.containsOne(errorLines, "Unable to launch")) {
EventBus.EVENT_BUS.fireEvent(new ProcessExitedAbnormallyEvent(this, process));
exitType = ProcessListener.ExitType.APPLICATION_ERROR;
} else
exitType = ProcessListener.ExitType.NORMAL;
@ -82,7 +81,7 @@ final class ExitWaiter implements Runnable {
watcher.accept(exitCode, exitType);
} catch (InterruptedException e) {
watcher.accept(1, ProcessListener.ExitType.NORMAL);
watcher.accept(1, ProcessListener.ExitType.INTERRUPTED);
}
}

View File

@ -0,0 +1,37 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.launch;
import java.io.IOException;
public class NotDecompressingNativesException extends IOException {
public NotDecompressingNativesException() {
}
public NotDecompressingNativesException(String message) {
super(message);
}
public NotDecompressingNativesException(String message, Throwable cause) {
super(message, cause);
}
public NotDecompressingNativesException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,40 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.launch;
import java.io.IOException;
/**
* Threw if unable to make file executable.
*/
public class PermissionException extends IOException {
public PermissionException() {
}
public PermissionException(String message) {
super(message);
}
public PermissionException(String message, Throwable cause) {
super(message, cause);
}
public PermissionException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,37 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.launch;
import java.io.IOException;
public class ProcessCreationException extends IOException {
public ProcessCreationException() {
}
public ProcessCreationException(String message) {
super(message);
}
public ProcessCreationException(String message, Throwable cause) {
super(message, cause);
}
public ProcessCreationException(Throwable cause) {
super(cause);
}
}

View File

@ -52,6 +52,7 @@ public interface ProcessListener {
enum ExitType {
JVM_ERROR,
APPLICATION_ERROR,
NORMAL
NORMAL,
INTERRUPTED
}
}