Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fb5048f10 | ||
|
|
0dd2e758e8 | ||
|
|
d1a562452a | ||
|
|
9205a6cbf4 | ||
|
|
4c8961f39a | ||
|
|
22bdc23a09 | ||
|
|
764cd16e21 | ||
|
|
7583f4651d | ||
|
|
13e931cda4 | ||
|
|
9743cf8351 | ||
|
|
e5ee5702f8 | ||
|
|
5bd5b25b77 | ||
|
|
55d59f3779 | ||
|
|
7d2ea0b77e | ||
|
|
1f30c299a5 | ||
|
|
c887d7e649 | ||
|
|
3fc7743a14 | ||
|
|
2a232f70db | ||
|
|
6fae131287 | ||
|
|
7ee7ef25f9 | ||
|
|
2bd4bc1472 | ||
|
|
aa170b647e | ||
|
|
4fa01216b1 | ||
|
|
a82eb996a4 | ||
|
|
9315684ee3 | ||
|
|
34f7774ce9 | ||
|
|
75031a04cc | ||
|
|
cfb0526cbb | ||
|
|
9fa56a9cd4 | ||
|
|
c4db9c9e12 | ||
|
|
71272371e4 | ||
|
|
91c6c4d233 | ||
|
|
8a6653a926 | ||
|
|
76caf84e39 | ||
|
|
0fee286b84 | ||
|
|
4af18a1575 | ||
|
|
d9293f4da8 | ||
|
|
db29f97397 | ||
|
|
573f41611d | ||
|
|
28ed103ec0 | ||
|
|
5e37750f00 | ||
|
|
5fe3cd9406 | ||
|
|
b00cc99408 | ||
|
|
9a66c3bba9 | ||
|
|
546921b445 | ||
|
|
8b698d3a4c | ||
|
|
c4608a949b | ||
|
|
bba1c559a3 | ||
|
|
bdee9ea72f | ||
|
|
520923ba08 | ||
|
|
936a74597f | ||
|
|
97132745cc | ||
|
|
2bb30f65f3 | ||
|
|
ec04d55793 | ||
|
|
88671db566 | ||
|
|
538f501bc0 | ||
|
|
3b75d41a2f | ||
|
|
110149d0e6 | ||
|
|
89b68781fa | ||
|
|
4cdc54b9c1 | ||
|
|
7673595a43 | ||
|
|
76a259c107 | ||
|
|
5f40d01fb4 | ||
|
|
df29d47c0a | ||
|
|
10d4084dde | ||
|
|
ba8cdb1b42 | ||
|
|
22732fee6a | ||
|
|
bfbc4613b9 | ||
|
|
92e5eecaf2 | ||
|
|
593159f4a6 | ||
|
|
53cad0e56a | ||
|
|
d61006dd67 | ||
|
|
eb565285b2 |
No files matched your search
@@ -21,7 +21,7 @@ import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -37,9 +37,7 @@ import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class Launcher extends Application {
|
||||
|
||||
@@ -82,6 +80,10 @@ public final class Launcher extends Application {
|
||||
UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args));
|
||||
|
||||
Logging.LOG.info("*** " + TITLE + " ***");
|
||||
Logging.LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||
Logging.LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
Logging.LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
Logging.LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
|
||||
launch(args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
@@ -135,32 +137,18 @@ public final class Launcher extends Application {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String i18n(String key) {
|
||||
try {
|
||||
return RESOURCE_BUNDLE.getString(key);
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
public static String i18n(String key, Object... formatArgs) {
|
||||
return String.format(i18n(key), formatArgs);
|
||||
}
|
||||
|
||||
public static final File MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
public static final File HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
public static final File LOG_DIRECTORY = new File(Launcher.HMCL_DIRECTORY, "logs");
|
||||
|
||||
public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
|
||||
public static final String VERSION = System.getProperty("hmcl.version.override", "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@");
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION));
|
||||
public static final IUpgrader UPGRADER = new AppDataUpgrader();
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
|
||||
|
||||
public static final String UPDATE_SERVER = "http://47.101.47.110";
|
||||
public static final String UPDATE_SERVER = "https://www.huangyuhui.net";
|
||||
public static final String CONTACT = UPDATE_SERVER + "/hmcl.php";
|
||||
public static final String PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
}
|
||||
@@ -17,14 +17,27 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import javax.swing.*;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
public final class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
checkDSTRootCAX3();
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
@@ -41,12 +54,35 @@ public final class Main {
|
||||
try {
|
||||
Class.forName("javafx.application.Application");
|
||||
} catch (ClassNotFoundException e) {
|
||||
showErrorAndExit("JavaFX is missing.\n"
|
||||
+ "If you are using Java 11 or above, please downgrade to Java 8 or 10.\n"
|
||||
+ "If you are using OpenJDK, please ensure OpenJFX is included.");
|
||||
showErrorAndExit(i18n("fatal.missing_javafx"));
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkDSTRootCAX3() {
|
||||
TrustManagerFactory tmf;
|
||||
try {
|
||||
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init((KeyStore) null);
|
||||
} catch (NoSuchAlgorithmException | KeyStoreException e) {
|
||||
LOG.log(Level.WARNING, "Failed to init TrustManagerFactory", e);
|
||||
// don't know what to do here
|
||||
return;
|
||||
}
|
||||
for (TrustManager tm : tmf.getTrustManagers()) {
|
||||
if (tm instanceof X509TrustManager) {
|
||||
for (X509Certificate cert : ((X509TrustManager) tm).getAcceptedIssuers()) {
|
||||
if ("CN=DST Root CA X3, O=Digital Signature Trust Co.".equals((cert.getSubjectDN().getName()))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
showWarningAndContinue(i18n("fatal.missing_dst_root_ca_x3"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
private static void showErrorAndExit(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("A fatal error has occurred, forcibly exiting.");
|
||||
@@ -54,4 +90,13 @@ public final class Main {
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that potential issues have been detected, and that the application may not function properly (but it can still run).
|
||||
*/
|
||||
private static void showWarningAndContinue(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("Potential issues have been detected.");
|
||||
JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.game;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -70,7 +71,7 @@ public class HMCLGameDownloadTask extends Task {
|
||||
NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||
cache,
|
||||
profile.getDependency().getProxy(),
|
||||
version.getDownloadInfo().getSha1()
|
||||
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||
).then(Task.of(v -> FileUtils.copyFile(cache, jar))));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,11 +22,13 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.ServerDisconnectException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
@@ -40,6 +42,7 @@ import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -47,6 +50,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LauncherHelper {
|
||||
public static final LauncherHelper INSTANCE = new LauncherHelper();
|
||||
@@ -76,7 +80,7 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
private void launch0(Profile profile, Account account, String selectedVersion, File scriptFile) {
|
||||
GameRepository repository = profile.getRepository();
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
@@ -90,18 +94,26 @@ public final class LauncherHelper {
|
||||
return dependencyManager.checkGameCompletionAsync(version);
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
|
||||
.then(new CurseCompletionTask(dependencyManager, selectedVersion))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(Launcher.i18n("account.methods"), variables -> {
|
||||
.then(var -> {
|
||||
try {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
} catch (ServerDisconnectException e) {
|
||||
variables.set("account",
|
||||
account.playOffline().orElseThrow(() -> e));
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if ("Curse".equals(configuration.getType()))
|
||||
return new CurseCompletionTask(dependencyManager, selectedVersion);
|
||||
else
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(i18n("account.methods"), variables -> {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
} catch (CredentialExpiredException e) {
|
||||
variables.set("account", DialogController.logIn(account));
|
||||
} catch (AuthenticationException e) {
|
||||
variables.set("account",
|
||||
account.playOffline().orElseThrow(() -> e));
|
||||
}
|
||||
}))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING)))
|
||||
@@ -119,12 +131,12 @@ public final class LauncherHelper {
|
||||
.then(variables -> {
|
||||
DefaultLauncher launcher = variables.get("launcher");
|
||||
if (scriptFile == null) {
|
||||
return new LaunchTask<>(launcher::launch).setName(Launcher.i18n("version.launch"));
|
||||
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
|
||||
} else {
|
||||
return new LaunchTask<>(() -> {
|
||||
launcher.makeLaunchScript(scriptFile);
|
||||
return null;
|
||||
}).setName(Launcher.i18n("version.launch_script"));
|
||||
}).setName(i18n("version.launch_script"));
|
||||
}
|
||||
})
|
||||
.then(Task.of(variables -> {
|
||||
@@ -141,7 +153,7 @@ public final class LauncherHelper {
|
||||
} else
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
|
||||
}))
|
||||
@@ -160,13 +172,31 @@ public final class LauncherHelper {
|
||||
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
if (!success) {
|
||||
if (!success && !Controllers.isStopped()) {
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
if (executor.getLastException() != null)
|
||||
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
|
||||
scriptFile == null ? Launcher.i18n("launch.failed") : Launcher.i18n("version.launch_script.failed"),
|
||||
MessageBox.ERROR_MESSAGE);
|
||||
// Check if the application has stopped
|
||||
// because onStop will be invoked if tasks fail when the executor service shut down.
|
||||
if (!Controllers.isStopped()) {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
Exception ex = executor.getLastException();
|
||||
if (ex != null) {
|
||||
String message;
|
||||
if (ex instanceof CurseCompletionException) {
|
||||
message = i18n("modpack.type.curse.error");
|
||||
} else if (ex instanceof PermissionException) {
|
||||
message = i18n("launch.failed.executable_permission");
|
||||
} else if (ex instanceof ProcessCreationException) {
|
||||
message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage();
|
||||
} else if (ex instanceof NotDecompressingNativesException) {
|
||||
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
|
||||
} else {
|
||||
message = StringUtils.getStackTrace(ex);
|
||||
}
|
||||
Controllers.dialog(message,
|
||||
scriptFile == null ? i18n("launch.failed") : i18n("version.launch_script.failed"),
|
||||
MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
launchingStepsPane.setExecutor(null);
|
||||
@@ -179,10 +209,12 @@ public final class LauncherHelper {
|
||||
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
|
||||
boolean flag = false;
|
||||
|
||||
// Without onAccept called, the launching operation will be terminated.
|
||||
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
|
||||
JavaVersion java = setting.getJavaVersion();
|
||||
if (java == null) {
|
||||
Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
@@ -190,27 +222,38 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
|
||||
// Minecraft 1.13 and later versions only support Java 8 or later.
|
||||
// Terminate launching operation.
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// LaunchWrapper will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
// cpw has claimed that he will make MinecraftForge of 1.13 and later versions able to run on Java 9.
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.java9"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.different_platform"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
@@ -261,15 +304,7 @@ public final class LauncherHelper {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
try {
|
||||
setResult(supplier.get());
|
||||
} catch (PermissionException e) {
|
||||
throw new I18nException(Launcher.i18n("launch.failed.executable_permission"), e);
|
||||
} catch (ProcessCreationException e) {
|
||||
throw new I18nException(Launcher.i18n("launch.failed.creating_process") + e.getLocalizedMessage(), e);
|
||||
} catch (NotDecompressingNativesException e) {
|
||||
throw new I18nException(Launcher.i18n("launch.failed.decompressing_natives") + e.getLocalizedMessage(), e);
|
||||
}
|
||||
setResult(supplier.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -388,10 +423,10 @@ public final class LauncherHelper {
|
||||
|
||||
switch (exitType) {
|
||||
case JVM_ERROR:
|
||||
logWindow.setTitle(Launcher.i18n("launch.failed.cannot_create_jvm"));
|
||||
logWindow.setTitle(i18n("launch.failed.cannot_create_jvm"));
|
||||
break;
|
||||
case APPLICATION_ERROR:
|
||||
logWindow.setTitle(Launcher.i18n("launch.failed.exited_abnormally"));
|
||||
logWindow.setTitle(i18n("launch.failed.exited_abnormally"));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public enum LoadingState {
|
||||
DEPENDENCIES("launch.state.dependencies"),
|
||||
@@ -33,6 +33,6 @@ public enum LoadingState {
|
||||
}
|
||||
|
||||
public String getLocalizedMessage() {
|
||||
return Launcher.i18n(key);
|
||||
return i18n(key);
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,12 @@ public final class ModpackHelper {
|
||||
throw new UnsupportedModpackException(file.toString());
|
||||
}
|
||||
|
||||
public static <T> ModpackConfiguration<T> readModpackConfiguration(File file) throws IOException {
|
||||
public static ModpackConfiguration<?> readModpackConfiguration(File file) throws IOException {
|
||||
if (!file.exists())
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
else
|
||||
try {
|
||||
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<T>>() {
|
||||
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
|
||||
}.getType());
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
|
||||
@@ -22,27 +22,19 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorBuildInfo;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerResponse;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloader;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
@@ -58,11 +50,11 @@ public final class Accounts {
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
||||
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.INSTANCE.getDownloadProvider())::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer))
|
||||
);
|
||||
|
||||
private static final Map<String, String> AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>();
|
||||
|
||||
public static String getAccountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY;
|
||||
else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY;
|
||||
@@ -78,33 +70,23 @@ public final class Accounts {
|
||||
return username + ":" + character;
|
||||
}
|
||||
|
||||
private static String downloadAuthlibInjector() throws Exception {
|
||||
AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo();
|
||||
File jar = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.jar");
|
||||
File local = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.txt");
|
||||
int buildNumber = 0;
|
||||
try {
|
||||
buildNumber = Integer.parseInt(FileUtils.readText(local));
|
||||
} catch (IOException | NumberFormatException ignore) {
|
||||
}
|
||||
if (buildNumber < buildInfo.getBuildNumber()) {
|
||||
new FileDownloadTask(new URL(buildInfo.getUrl()), jar).run();
|
||||
FileUtils.writeText(local, String.valueOf(buildInfo.getBuildNumber()));
|
||||
}
|
||||
return jar.getAbsolutePath();
|
||||
}
|
||||
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
|
||||
return Settings.SETTINGS.authlibInjectorServers.stream()
|
||||
.filter(server -> url.equals(server.getUrl()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
// this usually happens when migrating data from an older version
|
||||
AuthlibInjectorServer server;
|
||||
try {
|
||||
server = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
LOG.info("Migrated authlib injector server " + server);
|
||||
} catch (IOException e) {
|
||||
server = new AuthlibInjectorServer(url, url);
|
||||
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
|
||||
}
|
||||
|
||||
public static String getAuthlibInjectorServerName(String serverIp) throws Exception {
|
||||
if (AUTHLIB_INJECTOR_SERVER_NAMES.containsKey(serverIp))
|
||||
return AUTHLIB_INJECTOR_SERVER_NAMES.get(serverIp);
|
||||
else {
|
||||
AuthlibInjectorServerResponse response = Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(serverIp)), AuthlibInjectorServerResponse.class);
|
||||
AUTHLIB_INJECTOR_SERVER_NAMES.put(serverIp, response.getMeta().getServerName());
|
||||
return response.getMeta().getServerName();
|
||||
}
|
||||
}
|
||||
|
||||
public static TaskResult<String> getAuthlibInjectorServerNameAsync(AuthlibInjectorAccount account) {
|
||||
return Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(account.getServerBaseURL()));
|
||||
Settings.SETTINGS.authlibInjectorServers.add(server);
|
||||
return server;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
@@ -35,7 +36,6 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
|
||||
public final class Config implements Cloneable {
|
||||
|
||||
@@ -79,7 +79,7 @@ public final class Config implements Cloneable {
|
||||
public final StringProperty localization = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("downloadtype")
|
||||
public final IntegerProperty downloadType = new SimpleIntegerProperty();
|
||||
public final IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
|
||||
@SerializedName("configurations")
|
||||
public final ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
@@ -99,8 +99,10 @@ public final class Config implements Cloneable {
|
||||
@SerializedName("logLines")
|
||||
public final IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("authlibInjectorServerURLs")
|
||||
public final ObservableSet<String> authlibInjectorServerURLs = FXCollections.observableSet(new HashSet<>());
|
||||
@SerializedName("firstLaunch")
|
||||
public final BooleanProperty firstLaunch = new SimpleBooleanProperty(true);
|
||||
|
||||
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
@Override
|
||||
public Config clone() {
|
||||
|
||||
@@ -116,6 +116,7 @@ public final class Profile {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
if (vs == null || vs.isUsesGlobal()) {
|
||||
getGlobal().setGlobal(true); // always keep global.isGlobal = true
|
||||
getGlobal().setUsesGlobal(true);
|
||||
return getGlobal();
|
||||
} else
|
||||
return vs;
|
||||
|
||||
@@ -17,18 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Profiles {
|
||||
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
private Profiles() {
|
||||
}
|
||||
|
||||
public static String getProfileDisplayName(Profile profile) {
|
||||
switch (profile.getName()) {
|
||||
case Settings.DEFAULT_PROFILE:
|
||||
return Launcher.i18n("profile.default");
|
||||
case Settings.HOME_PROFILE:
|
||||
return Launcher.i18n("profile.home");
|
||||
case Profiles.DEFAULT_PROFILE:
|
||||
return i18n("profile.default");
|
||||
case Profiles.HOME_PROFILE:
|
||||
return i18n("profile.home");
|
||||
default:
|
||||
return profile.getName();
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -35,10 +36,12 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -51,7 +54,10 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class Settings {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
@@ -65,26 +71,27 @@ public class Settings {
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
public static final String SETTINGS_FILE_NAME = "hmcl.json";
|
||||
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
|
||||
|
||||
public static final Settings INSTANCE = new Settings();
|
||||
public static final Config SETTINGS = initSettings();
|
||||
|
||||
private final Config SETTINGS = initSettings();
|
||||
public static final Settings INSTANCE = new Settings();
|
||||
|
||||
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
|
||||
|
||||
private final boolean firstLaunch;
|
||||
|
||||
private Settings() {
|
||||
firstLaunch = SETTINGS.firstLaunch.get();
|
||||
|
||||
loadProxy();
|
||||
|
||||
for (Iterator<Map<Object, Object>> iterator = SETTINGS.accounts.iterator(); iterator.hasNext();) {
|
||||
Map<Object, Object> settings = iterator.next();
|
||||
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
|
||||
if (factory == null) {
|
||||
// unrecognized account type, so remove it.
|
||||
LOG.warning("Unrecognized account type, removing: " + settings);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
@@ -93,7 +100,7 @@ public class Settings {
|
||||
try {
|
||||
account = factory.fromStorage(settings, getProxy());
|
||||
} catch (Exception e) {
|
||||
// storage is malformed, delete.
|
||||
LOG.log(Level.WARNING, "Malformed account storage, removing: " + settings, e);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
@@ -101,7 +108,8 @@ public class Settings {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
checkAuthlibInjectorAccounts();
|
||||
SETTINGS.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
checkProfileMap();
|
||||
|
||||
save();
|
||||
@@ -115,7 +123,7 @@ public class Settings {
|
||||
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
|
||||
}
|
||||
|
||||
private Config initSettings() {
|
||||
private static Config initSettings() {
|
||||
Config c = new Config();
|
||||
if (SETTINGS_FILE.exists())
|
||||
try {
|
||||
@@ -137,6 +145,7 @@ public class Settings {
|
||||
public void save() {
|
||||
try {
|
||||
SETTINGS.accounts.clear();
|
||||
SETTINGS.firstLaunch.set(false);
|
||||
for (Account account : accounts.values()) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", Accounts.getAccountType(account));
|
||||
@@ -149,6 +158,10 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isFirstLaunch() {
|
||||
return firstLaunch;
|
||||
}
|
||||
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.commonDirectory.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
@@ -305,30 +318,17 @@ public class Settings {
|
||||
* AUTHLIB INJECTORS *
|
||||
****************************************/
|
||||
|
||||
public Set<String> getAuthlibInjectorServerURLs() {
|
||||
return SETTINGS.authlibInjectorServerURLs;
|
||||
}
|
||||
|
||||
public void removeAuthlibInjectorServerURL(String serverURL) {
|
||||
SETTINGS.authlibInjectorServerURLs.remove(serverURL);
|
||||
|
||||
checkAuthlibInjectorAccounts();
|
||||
save();
|
||||
}
|
||||
|
||||
public void addAuthlibInjectorServerURL(String serverURL) {
|
||||
SETTINGS.authlibInjectorServerURLs.add(serverURL);
|
||||
save();
|
||||
}
|
||||
|
||||
private void checkAuthlibInjectorAccounts() {
|
||||
for (Account account : getAccounts()) {
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorAccount injectorAccount = (AuthlibInjectorAccount) account;
|
||||
if (!SETTINGS.authlibInjectorServerURLs.contains(injectorAccount.getServerBaseURL()))
|
||||
deleteAccount(account);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
|
||||
* This method performs a check and removes the dangling accounts.
|
||||
* Don't call this before {@link #migrateAuthlibInjectorServers()} is called, otherwise old data would be lost.
|
||||
*/
|
||||
private void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.values().stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.filter(it -> !SETTINGS.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
|
||||
.collect(toList())
|
||||
.forEach(this::deleteAccount);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
@@ -561,8 +561,8 @@ public class Settings {
|
||||
|
||||
private void checkProfileMap() {
|
||||
if (getProfileMap().isEmpty()) {
|
||||
getProfileMap().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE));
|
||||
getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
|
||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, new Profile(Profiles.DEFAULT_PROFILE));
|
||||
getProfileMap().put(Profiles.HOME_PROFILE, new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,18 +28,18 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -74,14 +74,13 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
this.account = account;
|
||||
this.item = item;
|
||||
|
||||
title = new SimpleStringProperty(this, "title", Launcher.i18n("account") + " - " + account.getCharacter());
|
||||
title = new SimpleStringProperty(this, "title", i18n("account") + " - " + account.getCharacter());
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
|
||||
|
||||
FXUtils.setLimitWidth(this, 300);
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account)
|
||||
.subscribe(Schedulers.javafx(), variables -> lblServer.setText(variables.get("serverName")));
|
||||
lblServer.setText(((AuthlibInjectorAccount) account).getServer().getName());
|
||||
FXUtils.setLimitHeight(this, 182);
|
||||
} else {
|
||||
componentList.removeChildren(paneServer);
|
||||
|
||||
@@ -30,32 +30,34 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AddAccountPane extends StackPane {
|
||||
|
||||
@@ -64,27 +66,29 @@ public class AddAccountPane extends StackPane {
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblPassword;
|
||||
@FXML private JFXComboBox<String> cboType;
|
||||
@FXML private JFXComboBox<TwoLineListItem> cboServers;
|
||||
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
|
||||
@FXML private Label lblInjectorServer;
|
||||
@FXML private Hyperlink linkManageInjectorServers;
|
||||
@FXML private JFXDialogLayout layout;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private StackPane acceptPane;
|
||||
@FXML private JFXSpinner spinnerAccept;
|
||||
@FXML private SpinnerPane acceptPane;
|
||||
private final Consumer<Region> finalization;
|
||||
private final TransitionHandler transitionHandler;
|
||||
|
||||
public AddAccountPane(Consumer<Region> finalization) {
|
||||
this.finalization = finalization;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
|
||||
|
||||
transitionHandler = new TransitionHandler(acceptPane);
|
||||
acceptPane.getChildren().setAll(btnAccept);
|
||||
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
||||
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
||||
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
|
||||
cboServers.setPromptText(i18n("general.prompt.empty"));
|
||||
|
||||
loadServers();
|
||||
// workaround: otherwise the combox will be black
|
||||
if (!cboServers.getItems().isEmpty())
|
||||
cboServers.getSelectionModel().select(0);
|
||||
|
||||
cboType.getItems().setAll(Launcher.i18n("account.methods.offline"), Launcher.i18n("account.methods.yggdrasil"), Launcher.i18n("account.methods.authlib_injector"));
|
||||
cboType.getItems().setAll(i18n("account.methods.offline"), i18n("account.methods.yggdrasil"), i18n("account.methods.authlib_injector"));
|
||||
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
|
||||
txtPassword.setVisible(newValue.intValue() != 0);
|
||||
lblPassword.setVisible(newValue.intValue() != 0);
|
||||
@@ -95,13 +99,9 @@ public class AddAccountPane extends StackPane {
|
||||
});
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
// These two lines can eliminate black, don't know why.
|
||||
cboServers.getItems().setAll(new TwoLineListItem("", ""));
|
||||
cboServers.getSelectionModel().select(0);
|
||||
|
||||
txtPassword.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
|
||||
txtUsername.textProperty().addListener(it -> validateAcceptButton());
|
||||
txtPassword.textProperty().addListener(it -> validateAcceptButton());
|
||||
@@ -111,62 +111,53 @@ public class AddAccountPane extends StackPane {
|
||||
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
|
||||
}
|
||||
|
||||
private void loadServers() {
|
||||
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
|
||||
.flatMap(serverURL -> {
|
||||
try {
|
||||
return Stream.of(new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL));
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()))
|
||||
.subscribe(Task.of(Schedulers.javafx(), variables -> {
|
||||
cboServers.getItems().setAll(variables.<Collection<TwoLineListItem>>get("list"));
|
||||
if (!cboServers.getItems().isEmpty())
|
||||
cboServers.getSelectionModel().select(0);
|
||||
}));
|
||||
}
|
||||
|
||||
private void showSpinner() {
|
||||
transitionHandler.setContent(spinnerAccept, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
private void hideSpinner() {
|
||||
transitionHandler.setContent(btnAccept, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationAccept() {
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
String apiRoot = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()).map(TwoLineListItem::getSubtitle).orElse(null);
|
||||
showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult("create_account", () -> {
|
||||
AccountFactory<?> factory;
|
||||
switch (type) {
|
||||
case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break;
|
||||
case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break;
|
||||
case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break;
|
||||
default: throw new Error();
|
||||
}
|
||||
Object addtionalData;
|
||||
|
||||
return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy());
|
||||
}).finalized(Schedulers.javafx(), variables -> {
|
||||
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
||||
hideSpinner();
|
||||
finalization.accept(this);
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
finalization.accept(this);
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
hideSpinner();
|
||||
}).start();
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
AccountFactory<?> factory;
|
||||
switch (type) {
|
||||
case 0:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 1:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 2:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY);
|
||||
Optional<AuthlibInjectorServer> server = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem());
|
||||
if (server.isPresent()) {
|
||||
addtionalData = server.get();
|
||||
} else {
|
||||
lblCreationWarning.setText(i18n("account.failed.no_selected_server"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
acceptPane.showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
|
||||
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, addtionalData, Settings.INSTANCE.getProxy()))
|
||||
.finalized(Schedulers.javafx(), variables -> {
|
||||
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
||||
acceptPane.hideSpinner();
|
||||
finalization.accept(this);
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
finalization.accept(this);
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
acceptPane.hideSpinner();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -198,11 +189,11 @@ public class AddAccountPane extends StackPane {
|
||||
{
|
||||
setStyle("-fx-padding: 8px;");
|
||||
|
||||
cancel.setText(Launcher.i18n("button.cancel"));
|
||||
cancel.setText(i18n("button.cancel"));
|
||||
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
|
||||
cancel.setOnMouseClicked(e -> latch.countDown());
|
||||
|
||||
listBox.startCategory(Launcher.i18n("account.choose"));
|
||||
listBox.startCategory(i18n("account.choose"));
|
||||
|
||||
setCenter(listBox);
|
||||
|
||||
@@ -224,13 +215,13 @@ public class AddAccountPane extends StackPane {
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy());
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
|
||||
image = FXUtils.DEFAULT_ICON;
|
||||
image = null;
|
||||
}
|
||||
ImageView portraitView = new ImageView();
|
||||
portraitView.setSmooth(false);
|
||||
if (image == FXUtils.DEFAULT_ICON)
|
||||
portraitView.setImage(FXUtils.DEFAULT_ICON);
|
||||
else {
|
||||
if (image == null) {
|
||||
portraitView.setImage(Constants.DEFAULT_ICON.get());
|
||||
} else {
|
||||
portraitView.setImage(image);
|
||||
portraitView.setViewport(AccountHelper.getViewport(4));
|
||||
}
|
||||
@@ -262,25 +253,31 @@ public class AddAccountPane extends StackPane {
|
||||
}
|
||||
|
||||
public static String accountException(Exception exception) {
|
||||
if (exception instanceof InvalidCredentialsException) {
|
||||
return Launcher.i18n("account.failed.invalid_credentials");
|
||||
} else if (exception instanceof NoCharacterException) {
|
||||
return Launcher.i18n("account.failed.no_charactor");
|
||||
if (exception instanceof NoCharacterException) {
|
||||
return i18n("account.failed.no_character");
|
||||
} else if (exception instanceof ServerDisconnectException) {
|
||||
return Launcher.i18n("account.failed.connect_authentication_server");
|
||||
} else if (exception instanceof InvalidTokenException) {
|
||||
return Launcher.i18n("account.failed.invalid_token");
|
||||
} else if (exception instanceof InvalidPasswordException) {
|
||||
return Launcher.i18n("account.failed.invalid_password");
|
||||
return i18n("account.failed.connect_authentication_server");
|
||||
} else if (exception instanceof RemoteAuthenticationException) {
|
||||
RemoteAuthenticationException remoteException = (RemoteAuthenticationException) exception;
|
||||
String remoteMessage = remoteException.getRemoteMessage();
|
||||
if ("ForbiddenOperationException".equals(remoteException.getRemoteName()) && remoteMessage != null) {
|
||||
if (remoteMessage.contains("Invalid credentials"))
|
||||
return i18n("account.failed.invalid_credentials");
|
||||
else if (remoteMessage.contains("Invalid token"))
|
||||
return i18n("account.failed.invalid_token");
|
||||
else if (remoteMessage.contains("Invalid username or password"))
|
||||
return i18n("account.failed.invalid_password");
|
||||
}
|
||||
return exception.getMessage();
|
||||
} else {
|
||||
return exception.getClass() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return Launcher.i18n("account.methods.offline");
|
||||
else if (account instanceof AuthlibInjectorAccount) return Launcher.i18n("account.methods.authlib_injector");
|
||||
else if (account instanceof YggdrasilAccount) return Launcher.i18n("account.methods.yggdrasil");
|
||||
else throw new Error(Launcher.i18n("account.methods.no_method") + ": " + account);
|
||||
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
|
||||
else if (account instanceof AuthlibInjectorAccount) return i18n("account.methods.authlib_injector");
|
||||
else if (account instanceof YggdrasilAccount) return i18n("account.methods.yggdrasil");
|
||||
else throw new Error(i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
}
|
||||
@@ -23,19 +23,19 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AuthlibInjectorServerItem extends BorderPane {
|
||||
private final AuthlibInjectorServerInfo info;
|
||||
private final AuthlibInjectorServer server;
|
||||
|
||||
private final Label lblServerName = new Label();
|
||||
private final Label lblServerIp = new Label();
|
||||
|
||||
public AuthlibInjectorServerItem(AuthlibInjectorServerInfo info, Consumer<AuthlibInjectorServerItem> deleteCallback) {
|
||||
this.info = info;
|
||||
public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer<AuthlibInjectorServerItem> deleteCallback) {
|
||||
this.server = server;
|
||||
|
||||
lblServerName.setStyle("-fx-font-size: 15;");
|
||||
lblServerIp.setStyle("-fx-font-size: 10;");
|
||||
@@ -54,11 +54,11 @@ public final class AuthlibInjectorServerItem extends BorderPane {
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblServerName.setText(info.getServerName());
|
||||
lblServerIp.setText(info.getServerIp());
|
||||
lblServerName.setText(server.getName());
|
||||
lblServerIp.setText(server.getUrl());
|
||||
}
|
||||
|
||||
public AuthlibInjectorServerInfo getInfo() {
|
||||
return info;
|
||||
public AuthlibInjectorServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -4,49 +4,47 @@ import com.jfoenix.controls.*;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("account.injector.server"));
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.server"));
|
||||
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private Label lblServerIp;
|
||||
@FXML private Label lblServerUrl;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblServerWarning;
|
||||
@FXML private VBox listPane;
|
||||
@FXML private JFXTextField txtServerIp;
|
||||
@FXML private JFXTextField txtServerUrl;
|
||||
@FXML private JFXDialogLayout addServerPane;
|
||||
@FXML private JFXDialogLayout confirmServerPane;
|
||||
@FXML private JFXDialog dialog;
|
||||
@FXML private StackPane contentPane;
|
||||
@FXML private JFXSpinner spinner;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
@FXML private SpinnerPane nextPane;
|
||||
@FXML private JFXButton btnAddNext;
|
||||
|
||||
private final TransitionHandler transitionHandler;
|
||||
|
||||
private AuthlibInjectorServer serverBeingAdded;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
@@ -55,51 +53,35 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
getChildren().remove(dialog);
|
||||
dialog.setDialogContainer(this);
|
||||
|
||||
txtServerIp.textProperty().addListener((a, b, newValue) ->
|
||||
btnAddNext.setDisable(!txtServerIp.validate()));
|
||||
txtServerUrl.textProperty().addListener((a, b, newValue) ->
|
||||
btnAddNext.setDisable(!txtServerUrl.validate()));
|
||||
|
||||
loading();
|
||||
reload();
|
||||
}
|
||||
|
||||
private void removeServer(AuthlibInjectorServerItem item) {
|
||||
Settings.INSTANCE.removeAuthlibInjectorServerURL(item.getInfo().getServerIp());
|
||||
loading();
|
||||
Settings.SETTINGS.authlibInjectorServers.remove(item.getServer());
|
||||
reload();
|
||||
}
|
||||
|
||||
private void loading() {
|
||||
getChildren().remove(contentPane);
|
||||
spinner.setVisible(true);
|
||||
|
||||
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
|
||||
.flatMap(serverURL -> {
|
||||
try {
|
||||
return Stream.of(new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer));
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()))
|
||||
.subscribe(Task.of(Schedulers.javafx(), variables -> {
|
||||
listPane.getChildren().setAll(variables.<Collection<? extends Node>>get("list"));
|
||||
loadingCompleted();
|
||||
}));
|
||||
}
|
||||
|
||||
private void loadingCompleted() {
|
||||
getChildren().add(contentPane);
|
||||
spinner.setVisible(false);
|
||||
|
||||
if (Settings.INSTANCE.getAuthlibInjectorServerURLs().isEmpty())
|
||||
private void reload() {
|
||||
listPane.getChildren().setAll(
|
||||
Settings.SETTINGS.authlibInjectorServers.stream()
|
||||
.map(server -> new AuthlibInjectorServerItem(server, this::removeServer))
|
||||
.collect(toList()));
|
||||
if (Settings.SETTINGS.authlibInjectorServers.isEmpty()) {
|
||||
onAdd();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
txtServerIp.setText("");
|
||||
txtServerUrl.setText("");
|
||||
txtServerUrl.resetValidation();
|
||||
lblCreationWarning.setText("");
|
||||
addServerPane.setDisable(false);
|
||||
progressBar.setVisible(false);
|
||||
nextPane.hideSpinner();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@@ -110,26 +92,28 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
|
||||
@FXML
|
||||
private void onAddNext() {
|
||||
String serverIp = txtServerIp.getText();
|
||||
progressBar.setVisible(true);
|
||||
String url = fixInputUrl(txtServerUrl.getText());
|
||||
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(serverIp))
|
||||
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
progressBar.setVisible(false);
|
||||
addServerPane.setDisable(false);
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
nextPane.hideSpinner();
|
||||
addServerPane.setDisable(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(variables.get("serverName"));
|
||||
lblServerIp.setText(txtServerIp.getText());
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverIp).getProtocol()));
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else
|
||||
lblCreationWarning.setText(variables.<Exception>get("lastException").getLocalizedMessage());
|
||||
}).start();
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else {
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
@@ -140,11 +124,10 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
String ip = txtServerIp.getText();
|
||||
if (!ip.endsWith("/"))
|
||||
ip += "/";
|
||||
Settings.INSTANCE.addAuthlibInjectorServerURL(ip);
|
||||
loading();
|
||||
if (!Settings.INSTANCE.SETTINGS.authlibInjectorServers.contains(serverBeingAdded)) {
|
||||
Settings.INSTANCE.SETTINGS.authlibInjectorServers.add(serverBeingAdded);
|
||||
}
|
||||
reload();
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
@@ -160,4 +143,19 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
private String fixInputUrl(String url) {
|
||||
if (!url.endsWith("/")) {
|
||||
url += "/";
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private String resolveFetchExceptionMessage(Throwable exception) {
|
||||
if (exception instanceof IOException) {
|
||||
return i18n("account.failed.connect_injector_server");
|
||||
} else {
|
||||
return exception.getClass() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,7 +157,8 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void closeDialog(Region content) {
|
||||
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
decorator.closeDialog(content);
|
||||
}
|
||||
|
||||
@@ -174,6 +175,10 @@ public final class Controllers {
|
||||
getLeftPaneController().showUpdate();
|
||||
}
|
||||
|
||||
public static boolean isStopped() {
|
||||
return decorator == null;
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
mainPage = null;
|
||||
settingsPage = null;
|
||||
|
||||
@@ -27,6 +27,9 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
/**
|
||||
@@ -37,9 +40,9 @@ public class CrashWindow extends Stage {
|
||||
public CrashWindow(String text) {
|
||||
Label lblCrash = new Label();
|
||||
if (Launcher.UPDATE_CHECKER.isOutOfDate())
|
||||
lblCrash.setText(Launcher.i18n("launcher.crash_out_dated"));
|
||||
lblCrash.setText(i18n("launcher.crash_out_dated"));
|
||||
else
|
||||
lblCrash.setText(Launcher.i18n("launcher.crash"));
|
||||
lblCrash.setText(i18n("launcher.crash"));
|
||||
lblCrash.setWrapText(true);
|
||||
|
||||
TextArea textArea = new TextArea();
|
||||
@@ -47,7 +50,7 @@ public class CrashWindow extends Stage {
|
||||
textArea.setEditable(false);
|
||||
|
||||
Button btnContact = new Button();
|
||||
btnContact.setText(Launcher.i18n("launcher.contact"));
|
||||
btnContact.setText(i18n("launcher.contact"));
|
||||
btnContact.setOnMouseClicked(event -> FXUtils.openLink(Launcher.CONTACT));
|
||||
HBox box = new HBox();
|
||||
box.setStyle("-fx-padding: 8px;");
|
||||
@@ -65,7 +68,7 @@ public class CrashWindow extends Stage {
|
||||
Scene scene = new Scene(pane, 800, 480);
|
||||
setScene(scene);
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
setTitle(Launcher.i18n("message.error"));
|
||||
setTitle(i18n("message.error"));
|
||||
|
||||
setOnCloseRequest(e -> System.exit(1));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXDrawer;
|
||||
import com.jfoenix.controls.JFXHamburger;
|
||||
import com.jfoenix.svg.SVGGlyph;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
@@ -38,6 +42,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
@@ -45,6 +50,7 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
@@ -59,8 +65,8 @@ import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@@ -129,6 +135,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
private JFXButton btnClose;
|
||||
@FXML
|
||||
private HBox navLeft;
|
||||
@FXML
|
||||
private ImageView welcomeView;
|
||||
|
||||
public Decorator(Stage primaryStage, Node mainPage, String title) {
|
||||
this(primaryStage, mainPage, title, true, true);
|
||||
@@ -172,6 +180,19 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setContent(dialogPane);
|
||||
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(welcomeView.opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), new KeyValue(welcomeView.opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), e2 -> drawerWrapper.getChildren().remove(welcomeView))
|
||||
);
|
||||
nowAnimation.play();
|
||||
});
|
||||
if (!Settings.INSTANCE.isFirstLaunch() || Settings.INSTANCE.getLocale().getLocale() != Locale.CHINA)
|
||||
drawerWrapper.getChildren().remove(welcomeView);
|
||||
|
||||
if (!min) buttonsContainer.getChildren().remove(btnMin);
|
||||
if (!max) buttonsContainer.getChildren().remove(btnMax);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -30,19 +31,22 @@ import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -52,6 +56,7 @@ import java.net.URI;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -68,6 +73,10 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static InvalidationListener onInvalidating(Runnable action) {
|
||||
return arg -> action.run();
|
||||
}
|
||||
|
||||
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener((a, b, c) -> consumer.accept(c));
|
||||
}
|
||||
@@ -194,7 +203,7 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void loadFXML(Node node, String absolutePath) {
|
||||
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), Launcher.RESOURCE_BUNDLE);
|
||||
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), I18n.getResourceBundle());
|
||||
loader.setRoot(node);
|
||||
loader.setController(node);
|
||||
Lang.invoke((ExceptionalSupplier<Object, IOException>) loader::load);
|
||||
@@ -402,7 +411,32 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png");
|
||||
public static <T> StringConverter<T> stringConverter(Function<T, String> func) {
|
||||
return new StringConverter<T>() {
|
||||
|
||||
@Override
|
||||
public String toString(T object) {
|
||||
return object == null ? "" : func.apply(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromString(String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Callback<ListView<T>, ListCell<T>> jfxListCellFactory(Function<T, Node> graphicBuilder) {
|
||||
return view -> new JFXListCell<T>() {
|
||||
@Override
|
||||
public void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (!empty) {
|
||||
setGraphic(graphicBuilder.apply(item));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final Interpolator SINE = new Interpolator() {
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
@@ -90,7 +91,7 @@ public class InstallerController {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
|
||||
|
||||
if (!gameVersion.isPresent())
|
||||
Controllers.dialog(Launcher.i18n("version.cannot_read"));
|
||||
Controllers.dialog(i18n("version.cannot_read"));
|
||||
else
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -44,7 +45,7 @@ public class InstallerItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblInstallerArtifact.setText(artifact);
|
||||
lblInstallerVersion.setText(Launcher.i18n("archive.version") + ": " + version);
|
||||
lblInstallerVersion.setText(i18n("archive.version") + ": " + version);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -28,12 +28,11 @@ import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -49,6 +48,7 @@ import org.jackhuang.hmcl.ui.construct.ClassTitle;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
@@ -61,27 +61,27 @@ public final class LeftPaneController {
|
||||
private final VBox profilePane = new VBox();
|
||||
private final VBox accountPane = new VBox();
|
||||
private final IconedItem launcherSettingsItem;
|
||||
private final VersionListItem missingAccountItem = new VersionListItem(Launcher.i18n("account.missing"), Launcher.i18n("message.unknown"));
|
||||
private final VersionListItem missingAccountItem = new VersionListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
private final HashMap<Account, VersionListItem> items = new HashMap<>();
|
||||
|
||||
public LeftPaneController(AdvancedListBox leftPane) {
|
||||
this.leftPane = leftPane;
|
||||
|
||||
this.launcherSettingsItem = Lang.apply(new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), Launcher.i18n("settings.launcher")), iconedItem -> {
|
||||
this.launcherSettingsItem = Lang.apply(new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), i18n("settings.launcher")), iconedItem -> {
|
||||
iconedItem.prefWidthProperty().bind(leftPane.widthProperty());
|
||||
iconedItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
});
|
||||
|
||||
leftPane
|
||||
.add(new ClassTitle(Launcher.i18n("account").toUpperCase(), Lang.apply(new JFXButton(), button -> {
|
||||
.add(new ClassTitle(i18n("account").toUpperCase(), Lang.apply(new JFXButton(), button -> {
|
||||
button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10));
|
||||
button.getStyleClass().add("toggle-icon-tiny");
|
||||
button.setOnMouseClicked(e -> addNewAccount());
|
||||
})))
|
||||
.add(accountPane)
|
||||
.startCategory(Launcher.i18n("launcher").toUpperCase())
|
||||
.startCategory(i18n("launcher").toUpperCase())
|
||||
.add(launcherSettingsItem)
|
||||
.add(new ClassTitle(Launcher.i18n("profile.title").toUpperCase(), Lang.apply(new JFXButton(), button -> {
|
||||
.add(new ClassTitle(i18n("profile.title").toUpperCase(), Lang.apply(new JFXButton(), button -> {
|
||||
button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10));
|
||||
button.getStyleClass().add("toggle-icon-tiny");
|
||||
button.setOnMouseClicked(e ->
|
||||
@@ -122,7 +122,7 @@ public final class LeftPaneController {
|
||||
if (node instanceof RipplerContainer && node.getProperties().get("profile") instanceof String) {
|
||||
boolean current = Objects.equals(node.getProperties().get("profile"), profile.getName());
|
||||
((RipplerContainer) node).setSelected(current);
|
||||
((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? Launcher.i18n("profile.selected") : "");
|
||||
((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? i18n("profile.selected") : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -143,9 +143,9 @@ public final class LeftPaneController {
|
||||
}
|
||||
|
||||
private static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return Launcher.i18n("account.methods.offline");
|
||||
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount) return account.getUsername();
|
||||
else throw new Error(Launcher.i18n("account.methods.no_method") + ": " + account);
|
||||
else throw new Error(i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
|
||||
private void onAccountAdd(AccountAddedEvent event) {
|
||||
@@ -186,9 +186,9 @@ public final class LeftPaneController {
|
||||
} else
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount)
|
||||
Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account)
|
||||
.subscribe(Schedulers.javafx(), variables -> FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(variables.get("serverName"))));
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
|
||||
}
|
||||
|
||||
if (selectedAccount == account)
|
||||
ripplerContainer.setSelected(true);
|
||||
@@ -207,7 +207,7 @@ public final class LeftPaneController {
|
||||
}
|
||||
|
||||
public void showUpdate() {
|
||||
launcherSettingsItem.setText(Launcher.i18n("update.found"));
|
||||
launcherSettingsItem.setText(i18n("update.found"));
|
||||
launcherSettingsItem.setTextFill(Color.RED);
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ public final class LeftPaneController {
|
||||
Controllers.closeDialog(region.get());
|
||||
checkAccount();
|
||||
})).executor();
|
||||
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.installing"), ""));
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.installing"), ""));
|
||||
executor.start();
|
||||
showNewAccount = false;
|
||||
} catch (UnsupportedModpackException ignore) {
|
||||
|
||||
@@ -30,7 +30,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
@@ -43,6 +43,8 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
@@ -63,7 +65,7 @@ public final class LogWindow extends Stage {
|
||||
public LogWindow() {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
|
||||
setTitle(Launcher.i18n("logwindow.title"));
|
||||
setTitle(i18n("logwindow.title"));
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
@@ -47,7 +47,6 @@ import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -57,10 +56,11 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("main_page"));
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("main_page"));
|
||||
|
||||
private Profile profile;
|
||||
|
||||
@@ -95,10 +95,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
this.profile = event.getProfile();
|
||||
});
|
||||
|
||||
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), Launcher.i18n("install")));
|
||||
FXUtils.installTooltip(btnAdd, Launcher.i18n("install"));
|
||||
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), i18n("install")));
|
||||
FXUtils.installTooltip(btnAdd, i18n("install"));
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersionsAsync().start());
|
||||
FXUtils.installTooltip(btnRefresh, Launcher.i18n("button.refresh"));
|
||||
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
|
||||
}
|
||||
|
||||
private String modifyVersion(String gameVersion, String version) {
|
||||
@@ -117,13 +117,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
libraries.append(Launcher.i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
|
||||
libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
|
||||
libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
|
||||
libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,15 +138,15 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
item.setOnScriptButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Launcher.i18n("login.empty_username"));
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
if (repository.getRunDirectory(id).isDirectory())
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id));
|
||||
chooser.setTitle(Launcher.i18n("version.launch_script.save"));
|
||||
chooser.setTitle(i18n("version.launch_script.save"));
|
||||
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
? new FileChooser.ExtensionFilter(Launcher.i18n("extension.bat"), "*.bat")
|
||||
: new FileChooser.ExtensionFilter(Launcher.i18n("extension.sh"), "*.sh"));
|
||||
? new FileChooser.ExtensionFilter(i18n("extension.bat"), "*.bat")
|
||||
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
if (file != null)
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, file);
|
||||
@@ -158,25 +158,25 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
item.setOnUpdateButtonClicked(event -> {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(Launcher.i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
try {
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
|
||||
.then(Task.of(Schedulers.javafx(), () -> Controllers.closeDialog(region.get()))).executor();
|
||||
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.update"), ""));
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
|
||||
executor.start();
|
||||
} catch (UnsupportedModpackException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.unsupported"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.mismatched_type"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.invalid"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -187,10 +187,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
versionList.getStyleClass().add("option-list-view");
|
||||
FXUtils.setLimitWidth(versionList, 150);
|
||||
versionList.getItems().setAll(Lang.immutableListOf(
|
||||
Launcher.i18n("version.manage.rename"),
|
||||
Launcher.i18n("version.manage.remove"),
|
||||
Launcher.i18n("modpack.export"),
|
||||
Launcher.i18n("folder.game")
|
||||
i18n("version.manage.rename"),
|
||||
i18n("version.manage.remove"),
|
||||
i18n("modpack.export"),
|
||||
i18n("folder.game")
|
||||
));
|
||||
versionList.setOnMouseClicked(e -> {
|
||||
versionPopup.hide();
|
||||
@@ -214,7 +214,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Launcher.i18n("login.empty_username"));
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null);
|
||||
}
|
||||
|
||||
@@ -26,13 +26,14 @@ import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -42,7 +43,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class ModController {
|
||||
@FXML
|
||||
@@ -140,8 +140,8 @@ public final class ModController {
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(Launcher.i18n("mods.choose_mod"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(Launcher.i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
|
||||
chooser.setTitle(i18n("mods.choose_mod"));
|
||||
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
|
||||
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
|
||||
|
||||
// It's guaranteed that succeeded and failed are thread safe here.
|
||||
@@ -163,10 +163,10 @@ public final class ModController {
|
||||
}).with(Task.of(Schedulers.javafx(), variables -> {
|
||||
List<String> prompt = new LinkedList<>();
|
||||
if (!succeeded.isEmpty())
|
||||
prompt.add(Launcher.i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
if (!failed.isEmpty())
|
||||
prompt.add(Launcher.i18n("mods.add.failed", String.join(", ", failed)));
|
||||
Controllers.dialog(String.join("\n", prompt), Launcher.i18n("mods.add"));
|
||||
prompt.add(i18n("mods.add.failed", String.join(", ", failed)));
|
||||
Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
|
||||
loadMods(modManager, versionId);
|
||||
})).start();
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -40,7 +42,9 @@ public final class ModItem extends BorderPane {
|
||||
setCenter(modItem);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
FXUtils.installTooltip(btnRemove, Launcher.i18n("mods.remove"));
|
||||
JFXUtilities.runInFX(() -> {
|
||||
FXUtils.installTooltip(btnRemove, i18n("mods.remove"));
|
||||
});
|
||||
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
@@ -50,7 +54,7 @@ public final class ModItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
modItem.setTitle(info.getFileName());
|
||||
modItem.setSubtitle(info.getName() + ", " + Launcher.i18n("archive.version") + ": " + info.getVersion() + ", " + Launcher.i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + Launcher.i18n("archive.author") + ": " + info.getAuthors());
|
||||
modItem.setSubtitle(info.getName() + ", " + i18n("archive.version") + ": " + info.getVersion() + ", " + i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + i18n("archive.author") + ": " + info.getAuthors());
|
||||
chkEnabled.setSelected(info.isActive());
|
||||
chkEnabled.selectedProperty().addListener((a, b, newValue) ->
|
||||
info.activeProperty().set(newValue));
|
||||
|
||||
@@ -23,13 +23,14 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
@@ -54,7 +55,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||
|
||||
title = new SimpleStringProperty(this, "title",
|
||||
profile == null ? Launcher.i18n("profile.new") : Launcher.i18n("profile") + " - " + profileDisplayName);
|
||||
profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName);
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
@@ -49,7 +52,7 @@ import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("settings.launcher"));
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("settings.launcher"));
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtProxyHost;
|
||||
@@ -177,12 +180,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
|
||||
|
||||
FXUtils.installTooltip(btnUpdate, Launcher.i18n("update.tooltip"));
|
||||
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
checkUpdate();
|
||||
|
||||
// background
|
||||
backgroundItem.loadChildren(Collections.singletonList(
|
||||
backgroundItem.createChildren(Launcher.i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
));
|
||||
|
||||
FXUtils.bindString(backgroundItem.getTxtCustom(), Settings.INSTANCE.backgroundImageProperty());
|
||||
@@ -199,8 +202,8 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
// theme
|
||||
JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null);
|
||||
picker.setCustomColorText(Launcher.i18n("color.custom"));
|
||||
picker.setRecentColorsText(Launcher.i18n("color.recent"));
|
||||
picker.setCustomColorText(i18n("color.custom"));
|
||||
picker.setRecentColorsText(i18n("color.recent"));
|
||||
picker.getCustomColors().setAll(Arrays.stream(Theme.VALUES).map(Theme::getColor).map(Color::web).collect(Collectors.toList()));
|
||||
picker.setOnAction(e -> {
|
||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
||||
@@ -214,7 +217,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private void initBackgroundItemSubtitle() {
|
||||
switch (Settings.INSTANCE.getBackgroundImageType()) {
|
||||
case DEFAULT:
|
||||
backgroundItem.setSubtitle(Launcher.i18n("launcher.background.default"));
|
||||
backgroundItem.setSubtitle(i18n("launcher.background.default"));
|
||||
break;
|
||||
case CUSTOM:
|
||||
backgroundItem.setSubtitle(Settings.INSTANCE.getBackgroundImage());
|
||||
@@ -239,16 +242,16 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
btnUpdate.setVisible(Launcher.UPDATE_CHECKER.isOutOfDate());
|
||||
|
||||
if (Launcher.UPDATE_CHECKER.isOutOfDate()) {
|
||||
lblUpdateSub.setText(Launcher.i18n("update.newest_version", Launcher.UPDATE_CHECKER.getNewVersion().toString()));
|
||||
lblUpdateSub.setText(i18n("update.newest_version", Launcher.UPDATE_CHECKER.getNewVersion().toString()));
|
||||
lblUpdateSub.getStyleClass().setAll("update-label");
|
||||
|
||||
lblUpdate.setText(Launcher.i18n("update.found"));
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
} else {
|
||||
lblUpdateSub.setText(Launcher.i18n("update.latest"));
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(Launcher.i18n("update"));
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
@@ -33,8 +34,9 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -173,10 +175,12 @@ public final class VersionItem extends StackPane {
|
||||
btnLaunch.setGraphic(SVG.launch(Theme.blackFillBinding(), 15, 15));
|
||||
btnScript.setGraphic(SVG.script(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
FXUtils.installTooltip(btnSettings, Launcher.i18n("version.settings"));
|
||||
FXUtils.installTooltip(btnUpdate, Launcher.i18n("version.update"));
|
||||
FXUtils.installTooltip(btnLaunch, Launcher.i18n("version.launch"));
|
||||
FXUtils.installTooltip(btnScript, Launcher.i18n("version.launch_script"));
|
||||
JFXUtilities.runInFX(() -> {
|
||||
FXUtils.installTooltip(btnSettings, i18n("version.settings"));
|
||||
FXUtils.installTooltip(btnUpdate, i18n("version.update"));
|
||||
FXUtils.installTooltip(btnLaunch, i18n("version.launch"));
|
||||
FXUtils.installTooltip(btnScript, i18n("version.launch_script"));
|
||||
});
|
||||
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
FXUtils.limitSize(iconView, 32, 32);
|
||||
|
||||
@@ -26,12 +26,13 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -79,17 +80,17 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
browsePopup = new JFXPopup(browseList);
|
||||
managementPopup = new JFXPopup(managementList);
|
||||
|
||||
FXUtils.installTooltip(btnDelete, Launcher.i18n("version.manage.remove"));
|
||||
FXUtils.installTooltip(btnBrowseMenu, Launcher.i18n("settings.game.exploration"));
|
||||
FXUtils.installTooltip(btnManagementMenu, Launcher.i18n("settings.game.management"));
|
||||
FXUtils.installTooltip(btnExport, Launcher.i18n("modpack.export"));
|
||||
FXUtils.installTooltip(btnDelete, i18n("version.manage.remove"));
|
||||
FXUtils.installTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
|
||||
FXUtils.installTooltip(btnManagementMenu, i18n("settings.game.management"));
|
||||
FXUtils.installTooltip(btnExport, i18n("modpack.export"));
|
||||
}
|
||||
|
||||
public void load(String id, Profile profile) {
|
||||
this.version = id;
|
||||
this.profile = profile;
|
||||
|
||||
title.set(Launcher.i18n("settings.game") + " - " + id);
|
||||
title.set(i18n("settings.game") + " - " + id);
|
||||
|
||||
versionSettingsController.loadVersionSetting(profile, id);
|
||||
modController.setParentTab(tabPane);
|
||||
@@ -185,7 +186,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
Controllers.confirmDialog(Launcher.i18n("version.manage.remove.confirm", version), Launcher.i18n("message.confirm"), () -> {
|
||||
Controllers.confirmDialog(i18n("version.manage.remove.confirm", version), i18n("message.confirm"), () -> {
|
||||
if (profile.getRepository().removeVersionFromDisk(version)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
@@ -194,7 +195,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void renameVersion(Profile profile, String version) {
|
||||
Controllers.inputDialog(Launcher.i18n("version.manage.rename.message"), res -> {
|
||||
Controllers.inputDialog(i18n("version.manage.rename.message"), res -> {
|
||||
if (profile.getRepository().renameVersion(version, res)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
@@ -203,6 +204,6 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void exportVersion(Profile profile, String version) {
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Launcher.i18n("modpack.wizard"));
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), i18n("modpack.wizard"));
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import javafx.scene.control.Toggle;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ImagePickerItem;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -79,7 +80,7 @@ public final class VersionSettingsController {
|
||||
|
||||
@FXML
|
||||
private void initialize() {
|
||||
lblPhysicalMemory.setText(Launcher.i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB");
|
||||
lblPhysicalMemory.setText(i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB");
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
@@ -96,19 +97,17 @@ public final class VersionSettingsController {
|
||||
javaItem.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe"));
|
||||
|
||||
gameDirItem.loadChildren(Arrays.asList(
|
||||
gameDirItem.createChildren(Launcher.i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
|
||||
gameDirItem.createChildren(Launcher.i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
|
||||
gameDirItem.createChildren(i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
|
||||
gameDirItem.createChildren(i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
|
||||
));
|
||||
|
||||
globalItem.loadChildren(Arrays.asList(
|
||||
globalItem.createChildren(Launcher.i18n("settings.type.global"), true),
|
||||
globalItem.createChildren(Launcher.i18n("settings.type.special"), false)
|
||||
globalItem.createChildren(i18n("settings.type.global"), true),
|
||||
globalItem.createChildren(i18n("settings.type.special"), false)
|
||||
));
|
||||
}
|
||||
|
||||
public void loadVersionSetting(Profile profile, String versionId) {
|
||||
rootPane.getChildren().remove(advancedSettingsPane);
|
||||
|
||||
this.profile = profile;
|
||||
this.versionId = versionId;
|
||||
|
||||
@@ -173,6 +172,8 @@ public final class VersionSettingsController {
|
||||
else
|
||||
globalItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Boolean.FALSE).findFirst().ifPresent(it -> it.setSelected(true));
|
||||
globalItem.setToggleSelectedListener(newValue -> {
|
||||
// do not call versionSettings.setUsesGlobal(true/false)
|
||||
// because versionSettings can be the global one.
|
||||
if ((Boolean) newValue.getUserData())
|
||||
profile.globalizeVersionSetting(versionId);
|
||||
else
|
||||
@@ -182,7 +183,7 @@ public final class VersionSettingsController {
|
||||
});
|
||||
|
||||
versionSetting.usesGlobalProperty().setChangedListenerAndOperate(it ->
|
||||
globalItem.setSubtitle(Launcher.i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special")));
|
||||
globalItem.setSubtitle(i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special")));
|
||||
|
||||
gameDirItem.getGroup().getToggles().stream()
|
||||
.filter(it -> it.getUserData() == versionSetting.getGameDirType())
|
||||
@@ -235,18 +236,10 @@ public final class VersionSettingsController {
|
||||
gameDirItem.setSubtitle(profile.getRepository().getRunDirectory(versionId).getAbsolutePath());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onShowAdvanced() {
|
||||
if (!rootPane.getChildren().contains(advancedSettingsPane))
|
||||
rootPane.getChildren().add(advancedSettingsPane);
|
||||
else
|
||||
rootPane.getChildren().remove(advancedSettingsPane);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onExploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("extension.png"), "*.png"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
File iconFile = profile.getRepository().getVersionIcon(versionId);
|
||||
@@ -264,7 +257,7 @@ public final class VersionSettingsController {
|
||||
if (iconFile.exists())
|
||||
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
iconPickerItem.setImage(FXUtils.DEFAULT_ICON);
|
||||
iconPickerItem.setImage(Constants.DEFAULT_ICON.get());
|
||||
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,10 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> view.getChildren().remove(previousNode)));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
previousNode.setMouseTransparent((Boolean) previousNode.getProperties().get(MOUSE_TRANSPARENT));
|
||||
view.getChildren().remove(previousNode);
|
||||
}));
|
||||
nowAnimation.play();
|
||||
animation = nowAnimation;
|
||||
}
|
||||
@@ -93,13 +96,17 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
} else
|
||||
previousNode = NULL;
|
||||
|
||||
if (previousNode == currentNode)
|
||||
if (previousNode == newView)
|
||||
previousNode = NULL;
|
||||
|
||||
previousNode.getProperties().put(MOUSE_TRANSPARENT, previousNode.isMouseTransparent());
|
||||
previousNode.setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
view.getChildren().setAll(previousNode, currentNode);
|
||||
}
|
||||
|
||||
private static final String MOUSE_TRANSPARENT = "TransitionHandler.MOUSE_TRANSPARENT";
|
||||
private static final StackPane NULL = new StackPane();
|
||||
}
|
||||
@@ -26,11 +26,12 @@ import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -54,7 +55,7 @@ public class FileItem extends BorderPane {
|
||||
right.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
right.setOnMouseClicked(e -> onExplore());
|
||||
FXUtils.installTooltip(right, Launcher.i18n("button.edit"));
|
||||
FXUtils.installTooltip(right, i18n("button.edit"));
|
||||
setRight(right);
|
||||
|
||||
Tooltip tip = new Tooltip();
|
||||
|
||||
@@ -15,7 +15,9 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -41,7 +43,7 @@ public final class ImagePickerItem extends BorderPane {
|
||||
selectButton.onMouseClickedProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
FXUtils.installTooltip(selectButton, Launcher.i18n("button.edit"));
|
||||
FXUtils.installTooltip(selectButton, i18n("button.edit"));
|
||||
|
||||
HBox hBox = new HBox();
|
||||
hBox.getChildren().setAll(imageView, selectButton);
|
||||
|
||||
@@ -20,17 +20,19 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import javax.swing.*;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class MessageBox {
|
||||
private MessageBox() {
|
||||
}
|
||||
|
||||
private static final String TITLE = Launcher.i18n("message.info");
|
||||
private static final String TITLE = i18n("message.info");
|
||||
|
||||
/**
|
||||
* User Operation: Yes
|
||||
|
||||
@@ -23,10 +23,11 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
@@ -91,8 +92,8 @@ public final class MessageDialogPane extends StackPane {
|
||||
Optional.ofNullable(onCancel).ifPresent(Runnable::run);
|
||||
});
|
||||
|
||||
acceptButton.setText(Launcher.i18n("button.yes"));
|
||||
cancelButton.setText(Launcher.i18n("button.no"));
|
||||
acceptButton.setText(i18n("button.yes"));
|
||||
cancelButton.setText(i18n("button.no"));
|
||||
|
||||
actions.getChildren().add(cancelButton);
|
||||
}
|
||||
|
||||
@@ -13,16 +13,17 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MultiColorItem extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Launcher.i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Launcher.i18n("selector.choose_file"));
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
|
||||
|
||||
private final ToggleGroup group = new ToggleGroup();
|
||||
private final JFXColorPicker colorPicker = new JFXColorPicker();
|
||||
|
||||
@@ -37,19 +37,20 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MultiFileItem extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Launcher.i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Launcher.i18n("selector.choose_file"));
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
|
||||
private final BooleanProperty directory = new SimpleBooleanProperty(this, "directory", false);
|
||||
private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
|
||||
|
||||
@@ -147,7 +148,7 @@ public class MultiFileItem extends ComponentList {
|
||||
|
||||
public void onExploreJavaDir() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(Launcher.i18n(getChooserTitle()));
|
||||
chooser.setTitle(i18n(getChooserTitle()));
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null)
|
||||
txtCustom.setText(selectedDir.getAbsolutePath());
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public class SpinnerPane extends StackPane {
|
||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
||||
private final JFXSpinner spinner = new JFXSpinner();
|
||||
private final StackPane contentPane = new StackPane();
|
||||
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
|
||||
|
||||
public SpinnerPane() {
|
||||
getChildren().setAll(contentPane);
|
||||
|
||||
content.addListener((a, b, newValue) -> contentPane.getChildren().setAll(newValue));
|
||||
}
|
||||
|
||||
public void showSpinner() {
|
||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
public void hideSpinner() {
|
||||
transitionHandler.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
public Node getContent() {
|
||||
return content.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> contentProperty() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(Node content) {
|
||||
this.content.set(content);
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -19,13 +19,14 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.wizard.AbstractWizardDisplayer;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,7 +39,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
Controllers.navigate(null);
|
||||
});
|
||||
|
||||
pane.setTitle(Launcher.i18n("message.doing"));
|
||||
pane.setTitle(i18n("message.doing"));
|
||||
pane.setProgress(Double.MAX_VALUE);
|
||||
if (settings.containsKey("title")) {
|
||||
Object title = settings.get("title");
|
||||
@@ -66,7 +67,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
Controllers.dialog(Launcher.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
|
||||
Controllers.dialog(i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
return;
|
||||
@@ -74,7 +75,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
Controllers.dialog(appendix, Launcher.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
|
||||
Controllers.dialog(appendix, i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
|
||||
import org.jackhuang.hmcl.download.game.GameAssetRefreshTask;
|
||||
@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.task.TaskListener;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -62,29 +63,29 @@ public final class TaskListPane extends StackPane {
|
||||
return;
|
||||
|
||||
if (task instanceof GameAssetRefreshTask) {
|
||||
task.setName(Launcher.i18n("assets.download"));
|
||||
task.setName(i18n("assets.download"));
|
||||
} else if (task instanceof GameAssetDownloadTask) {
|
||||
task.setName(Launcher.i18n("assets.download_all"));
|
||||
task.setName(i18n("assets.download_all"));
|
||||
} else if (task instanceof ForgeInstallTask) {
|
||||
task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.forge")));
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
|
||||
} else if (task instanceof LiteLoaderInstallTask) {
|
||||
task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.liteloader")));
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.liteloader")));
|
||||
} else if (task instanceof OptiFineInstallTask) {
|
||||
task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.optifine")));
|
||||
task.setName(i18n("install.installer.install", i18n("install.installer.optifine")));
|
||||
} else if (task instanceof CurseCompletionTask) {
|
||||
task.setName(Launcher.i18n("modpack.type.curse.completion"));
|
||||
task.setName(i18n("modpack.type.curse.completion"));
|
||||
} else if (task instanceof ModpackInstallTask) {
|
||||
task.setName(Launcher.i18n("modpack.installing"));
|
||||
task.setName(i18n("modpack.installing"));
|
||||
} else if (task instanceof CurseInstallTask) {
|
||||
task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.curse")));
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.curse")));
|
||||
} else if (task instanceof MultiMCModpackInstallTask) {
|
||||
task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.multimc")));
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.multimc")));
|
||||
} else if (task instanceof HMCLModpackInstallTask) {
|
||||
task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.hmcl")));
|
||||
task.setName(i18n("modpack.install", i18n("modpack.type.hmcl")));
|
||||
} else if (task instanceof HMCLModpackExportTask) {
|
||||
task.setName(Launcher.i18n("modpack.export"));
|
||||
task.setName(i18n("modpack.export"));
|
||||
} else if (task instanceof MinecraftInstanceTask) {
|
||||
task.setName(Launcher.i18n("modpack.scan"));
|
||||
task.setName(i18n("modpack.scan"));
|
||||
}
|
||||
|
||||
ProgressListNode node = new ProgressListNode(task);
|
||||
|
||||
@@ -22,7 +22,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
@@ -30,12 +30,11 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.Launcher.i18n;
|
||||
|
||||
class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
private final InstallerWizardProvider provider;
|
||||
private final WizardController controller;
|
||||
@@ -100,7 +99,7 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("settings.tabs.installers");
|
||||
return i18n("settings.tabs.installers");
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
@@ -109,24 +108,24 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(Launcher.i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
|
||||
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
|
||||
btnForge.setDisable(provider.getForge() != null);
|
||||
if (provider.getForge() != null || controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge")));
|
||||
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge")));
|
||||
else
|
||||
lblForge.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.forge")));
|
||||
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
|
||||
|
||||
btnLiteLoader.setDisable(provider.getLiteLoader() != null);
|
||||
if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader")));
|
||||
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader")));
|
||||
else
|
||||
lblLiteLoader.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.liteloader")));
|
||||
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
|
||||
|
||||
btnOptiFine.setDisable(provider.getOptiFine() != null);
|
||||
if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine")));
|
||||
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine")));
|
||||
else
|
||||
lblOptiFine.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.optifine")));
|
||||
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
@@ -30,12 +30,11 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.Launcher.i18n;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class DownloadWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
@@ -69,7 +68,7 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
return null;
|
||||
|
||||
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_CURSEFORGE_MANIFEST), Modpack.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
@@ -78,8 +77,8 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", Launcher.i18n("install.success"));
|
||||
settings.put("failure_message", Launcher.i18n("install.failed"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
switch (Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1)) {
|
||||
case 0: return finishVersionDownloadingAsync(settings);
|
||||
@@ -102,10 +101,10 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
case 1:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + subStep + ", settings: " + settings);
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + subStep + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings);
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,11 @@ package org.jackhuang.hmcl.ui.download;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -49,7 +50,7 @@ public final class InstallTypePage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("install.select");
|
||||
return i18n("install.select");
|
||||
}
|
||||
|
||||
public static final String INSTALL_TYPE = "INSTALL_TYPE";
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -80,8 +81,8 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", Launcher.i18n("install.success"));
|
||||
settings.put("failure_message", Launcher.i18n("install.failed"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
Task ret = Task.empty();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -75,30 +76,30 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
String gameVersion = ((RemoteVersion<?>) controller.getSettings().get("game")).getGameVersion();
|
||||
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
|
||||
hasVersion.setMessage(Launcher.i18n("install.new_game.already_exists"));
|
||||
hasVersion.setMessage(i18n("install.new_game.already_exists"));
|
||||
txtName.getValidators().add(hasVersion);
|
||||
txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate()));
|
||||
txtName.setText(gameVersion);
|
||||
|
||||
btnForge.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 0);
|
||||
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
|
||||
});
|
||||
|
||||
btnLiteLoader.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 1);
|
||||
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
|
||||
});
|
||||
|
||||
btnOptiFine.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 2);
|
||||
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("install.new_game");
|
||||
return i18n("install.new_game");
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
@@ -107,21 +108,21 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(Launcher.i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
|
||||
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
|
||||
if (controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.forge")) + ": " + getVersion("forge"));
|
||||
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + getVersion("forge"));
|
||||
else
|
||||
lblForge.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.forge")));
|
||||
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
|
||||
|
||||
if (controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
|
||||
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
|
||||
else
|
||||
lblLiteLoader.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.liteloader")));
|
||||
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
|
||||
|
||||
if (controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
|
||||
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
|
||||
else
|
||||
lblOptiFine.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.optifine")));
|
||||
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
@@ -75,28 +76,28 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
Profile profile = (Profile) controller.getSettings().get("PROFILE");
|
||||
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(Launcher.i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) Platform.runLater(() -> Controllers.navigate(null));
|
||||
else {
|
||||
controller.getSettings().put(MODPACK_FILE, selectedFile);
|
||||
lblModpackLocation.setText(selectedFile.getAbsolutePath());
|
||||
txtModpackName.getValidators().addAll(
|
||||
new Validator(Launcher.i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
|
||||
new Validator(Launcher.i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
|
||||
new Validator(i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
|
||||
);
|
||||
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile);
|
||||
controller.getSettings().put(MODPACK_CURSEFORGE_MANIFEST, manifest);
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
} catch (UnsupportedModpackException e) {
|
||||
txtModpackName.setText(Launcher.i18n("modpack.task.install.error"));
|
||||
txtModpackName.setText(i18n("modpack.task.install.error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,17 +119,17 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
if (manifest != null) {
|
||||
WebStage stage = new WebStage();
|
||||
stage.getWebView().getEngine().loadContent(manifest.getDescription());
|
||||
stage.setTitle(Launcher.i18n("modpack.wizard.step.3"));
|
||||
stage.setTitle(i18n("modpack.wizard.step.3"));
|
||||
stage.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("modpack.task.install");
|
||||
return i18n("modpack.task.install");
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE = "MODPACK_FILE";
|
||||
public static final String MODPACK_NAME = "MODPACK_NAME";
|
||||
public static final String MODPACK_CURSEFORGE_MANIFEST = "CURSEFORGE_MANIFEST";
|
||||
public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST";
|
||||
}
|
||||
@@ -23,12 +23,13 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -57,15 +58,15 @@ public final class VersionsPageItem extends StackPane {
|
||||
if (remoteVersion.getTag() instanceof GameRemoteVersionTag) {
|
||||
switch (((GameRemoteVersionTag) remoteVersion.getTag()).getType()) {
|
||||
case RELEASE:
|
||||
lblGameVersion.setText(Launcher.i18n("version.game.release"));
|
||||
lblGameVersion.setText(i18n("version.game.release"));
|
||||
imageView.setImage(new Image("/assets/img/icon.png", 32, 32, false, true));
|
||||
break;
|
||||
case SNAPSHOT:
|
||||
lblGameVersion.setText(Launcher.i18n("version.game.snapshot"));
|
||||
lblGameVersion.setText(i18n("version.game.snapshot"));
|
||||
imageView.setImage(new Image("/assets/img/command.png", 32, 32, false, true));
|
||||
break;
|
||||
default:
|
||||
lblGameVersion.setText(Launcher.i18n("version.game.old"));
|
||||
lblGameVersion.setText(i18n("version.game.old"));
|
||||
imageView.setImage(new Image("/assets/img/grass.png", 32, 32, false, true));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TreeItem;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.game.ModAdviser;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -43,6 +42,7 @@ import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -151,23 +151,23 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("modpack.wizard.step.2.title");
|
||||
return i18n("modpack.wizard.step.2.title");
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
|
||||
private static final Map<String, String> TRANSLATION = mapOf(
|
||||
pair("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")),
|
||||
pair("minecraft/saves", Launcher.i18n("modpack.files.saves")),
|
||||
pair("minecraft/mods", Launcher.i18n("modpack.files.mods")),
|
||||
pair("minecraft/config", Launcher.i18n("modpack.files.config")),
|
||||
pair("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")),
|
||||
pair("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")),
|
||||
pair("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")),
|
||||
pair("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")),
|
||||
pair("minecraft/dumps", Launcher.i18n("modpack.files.dumps")),
|
||||
pair("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")),
|
||||
pair("minecraft/scripts", Launcher.i18n("modpack.files.scripts"))
|
||||
pair("minecraft/servers.dat", i18n("modpack.files.servers_dat")),
|
||||
pair("minecraft/saves", i18n("modpack.files.saves")),
|
||||
pair("minecraft/mods", i18n("modpack.files.mods")),
|
||||
pair("minecraft/config", i18n("modpack.files.config")),
|
||||
pair("minecraft/liteconfig", i18n("modpack.files.liteconfig")),
|
||||
pair("minecraft/resourcepacks", i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/resources", i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/options.txt", i18n("modpack.files.options_txt")),
|
||||
pair("minecraft/optionsshaders.txt", i18n("modpack.files.optionsshaders_txt")),
|
||||
pair("minecraft/mods/VoxelMods", i18n("modpack.files.mods.voxelmods")),
|
||||
pair("minecraft/dumps", i18n("modpack.files.dumps")),
|
||||
pair("minecraft/blueprints", i18n("modpack.files.blueprints")),
|
||||
pair("minecraft/scripts", i18n("modpack.files.scripts"))
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
@@ -81,8 +82,8 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
@FXML
|
||||
private void onNext() {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(Launcher.i18n("modpack.wizard.step.initialization.save"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
|
||||
fileChooser.setTitle(i18n("modpack.wizard.step.initialization.save"));
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File file = fileChooser.showSaveDialog(Controllers.getStage());
|
||||
if (file == null) {
|
||||
Controllers.navigate(null);
|
||||
@@ -109,7 +110,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("modpack.wizard.step.1.title");
|
||||
return i18n("modpack.wizard.step.1.title");
|
||||
}
|
||||
|
||||
public static final String MODPACK_NAME = "modpack.name";
|
||||
|
||||
@@ -20,10 +20,7 @@ package org.jackhuang.hmcl.ui.wizard;
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Stack;
|
||||
import java.util.*;
|
||||
|
||||
public class WizardController implements Navigation {
|
||||
private final WizardDisplayer displayer;
|
||||
@@ -47,6 +44,10 @@ public class WizardController implements Navigation {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public List<Node> getPages() {
|
||||
return Collections.unmodifiableList(pages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
Objects.requireNonNull(provider);
|
||||
@@ -81,6 +82,10 @@ public class WizardController implements Navigation {
|
||||
|
||||
@Override
|
||||
public void onPrev(boolean cleanUp) {
|
||||
if (!canPrev()) {
|
||||
throw new IllegalStateException("Cannot go backward since this is the back page. Pages: " + pages);
|
||||
}
|
||||
|
||||
Node page = pages.pop();
|
||||
if (cleanUp && page instanceof WizardPage)
|
||||
((WizardPage) page).cleanup(settings);
|
||||
|
||||
@@ -23,17 +23,18 @@ import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.*;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -49,7 +50,7 @@ import java.util.zip.GZIPInputStream;
|
||||
*/
|
||||
public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
private void launchNewerVersion(List<String> args, File jar) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InvocationTargetException, IllegalAccessException {
|
||||
private void launchNewerVersion(List<String> args, File jar) throws IOException, ReflectiveOperationException {
|
||||
try (JarFile jarFile = new JarFile(jar)) {
|
||||
String mainClass = jarFile.getManifest().getMainAttributes().getValue("Main-Class");
|
||||
if (mainClass == null)
|
||||
@@ -91,7 +92,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
}
|
||||
} catch (JsonParseException ex) {
|
||||
f.delete();
|
||||
} catch (IOException | NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException | ClassNotFoundException t) {
|
||||
} catch (IOException | ReflectiveOperationException t) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to execute newer version application", t);
|
||||
AppDataUpgraderPackGzTask.HMCL_VER_FILE.delete(); // delete version json, let HMCL re-download the newer version.
|
||||
}
|
||||
@@ -113,7 +114,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
Task task = new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
@@ -131,7 +132,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
Task task = new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
@@ -153,7 +154,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e);
|
||||
OperatingSystem.setClipboard(url);
|
||||
MessageBox.show(Launcher.i18n("update.no_browser"));
|
||||
MessageBox.show(i18n("update.no_browser"));
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
@@ -183,7 +184,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash));
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -233,7 +234,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash));
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,14 +19,13 @@ package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
@@ -35,6 +34,7 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -57,11 +57,11 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
URL url = requestDownloadLink();
|
||||
if (url == null) return;
|
||||
File newf = new File(url.getFile());
|
||||
Controllers.dialog(Launcher.i18n("message.downloading"));
|
||||
Controllers.dialog(i18n("message.downloading"));
|
||||
Task task = new FileDownloadTask(url, newf);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
try {
|
||||
new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath())
|
||||
|
||||
@@ -27,9 +27,10 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
@@ -78,8 +79,10 @@ public final class UpdateChecker {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (Launcher.VERSION.contains("@"))
|
||||
if (isDevelopmentVersion(Launcher.VERSION)) {
|
||||
LOG.info("Current version is a development version, skip updating");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
versionString = http.getResult();
|
||||
@@ -87,9 +90,9 @@ public final class UpdateChecker {
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
Logging.LOG.warning("Unable to check update...");
|
||||
LOG.warning("Unable to check update...");
|
||||
if (showMessage)
|
||||
MessageBox.show(Launcher.i18n("update.failed"));
|
||||
MessageBox.show(i18n("update.failed"));
|
||||
} else if (base.compareTo(value) < 0)
|
||||
outOfDate = true;
|
||||
if (outOfDate)
|
||||
@@ -103,6 +106,11 @@ public final class UpdateChecker {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isDevelopmentVersion(String version) {
|
||||
return version.contains("@") || // eg. @HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@
|
||||
version.contains("SNAPSHOT"); // eg. 3.1.SNAPSHOT
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <b>cached</b> newest version number, use "process" method to
|
||||
* download!
|
||||
@@ -124,12 +132,13 @@ public final class UpdateChecker {
|
||||
return new TaskResult<Map<String, String>>() {
|
||||
@Override
|
||||
public void execute() {
|
||||
if (download_link == null)
|
||||
if (download_link == null) {
|
||||
try {
|
||||
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL(Launcher.UPDATE_SERVER + "/hmcl/update_link.php")), Map.class);
|
||||
} catch (JsonSyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to get update link.", e);
|
||||
LOG.log(Level.SEVERE, "Failed to get update link.", e);
|
||||
}
|
||||
}
|
||||
setResult(download_link);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,17 @@ import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import static java.util.Collections.newSetFromMap;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
@@ -34,21 +39,21 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static final HashMap<String, String> SOURCE = new HashMap<String, String>() {
|
||||
private static final Map<String, String> SOURCE = new HashMap<String, String>() {
|
||||
{
|
||||
put("javafx.fxml.LoadException", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("Location is not set", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("UnsatisfiedLinkError", Launcher.i18n("crash.user_fault"));
|
||||
put("java.lang.NoClassDefFoundError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.VerifyError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchMethodError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchFieldError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("netscape.javascript.JSException", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.IncompatibleClassChangeError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.ClassFormatError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("javafx.fxml.LoadException", i18n("crash.NoClassDefFound"));
|
||||
put("Location is not set", i18n("crash.NoClassDefFound"));
|
||||
put("UnsatisfiedLinkError", i18n("crash.user_fault"));
|
||||
put("java.lang.NoClassDefFoundError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.VerifyError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchMethodError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchFieldError", i18n("crash.NoClassDefFound"));
|
||||
put("netscape.javascript.JSException", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.IncompatibleClassChangeError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.ClassFormatError", i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.OutOfMemoryError", "FUCKING MEMORY LIMIT!");
|
||||
put("Trampoline", Launcher.i18n("launcher.update_java"));
|
||||
put("com.sun.javafx.css.StyleManager.findMatchingStyles", Launcher.i18n("launcher.update_java"));
|
||||
put("Trampoline", i18n("launcher.update_java"));
|
||||
put("com.sun.javafx.css.StyleManager.findMatchingStyles", i18n("launcher.update_java"));
|
||||
put("NoSuchAlgorithmException", "Has your operating system been installed completely or is a ghost system?");
|
||||
}
|
||||
};
|
||||
@@ -59,11 +64,11 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
if (s.contains(entry.getKey())) {
|
||||
if (StringUtils.isNotBlank(entry.getValue())) {
|
||||
String info = entry.getValue();
|
||||
Logging.LOG.severe(info);
|
||||
LOG.severe(info);
|
||||
try {
|
||||
MessageBox.show(info);
|
||||
} catch (Throwable t) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to show message", t);
|
||||
LOG.log(Level.SEVERE, "Unable to show message", t);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -71,17 +76,21 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Set<String> CAUGHT_EXCEPTIONS = newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
String stackTrace = StringUtils.getStackTrace(e);
|
||||
if (!stackTrace.contains("org.jackhuang"))
|
||||
return;
|
||||
|
||||
if (THROWABLE_SET.contains(stackTrace))
|
||||
return;
|
||||
THROWABLE_SET.add(stackTrace);
|
||||
LOG.log(Level.SEVERE, "Uncaught exception in thread " + t.getName(), e);
|
||||
|
||||
try {
|
||||
String stackTrace = StringUtils.getStackTrace(e);
|
||||
if (!stackTrace.contains("org.jackhuang"))
|
||||
return;
|
||||
|
||||
if (CAUGHT_EXCEPTIONS.contains(stackTrace))
|
||||
return;
|
||||
CAUGHT_EXCEPTIONS.add(stackTrace);
|
||||
|
||||
String text = "---- Hello Minecraft! Crash Report ----\n" +
|
||||
" Version: " + Launcher.VERSION + "\n" +
|
||||
" Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" +
|
||||
@@ -93,21 +102,18 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n";
|
||||
|
||||
Logging.LOG.log(Level.SEVERE, text);
|
||||
LOG.log(Level.SEVERE, text);
|
||||
|
||||
if (checkThrowable(e)) {
|
||||
Platform.runLater(() -> new CrashWindow(text).show());
|
||||
if (!Launcher.UPDATE_CHECKER.isOutOfDate())
|
||||
reportToServer(text);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to caught exception", ex);
|
||||
Logging.LOG.log(Level.SEVERE, "There is the original exception", e);
|
||||
} catch (Throwable handlingException) {
|
||||
LOG.log(Level.SEVERE, "Unable to handle uncaught exception", handlingException);
|
||||
}
|
||||
}
|
||||
|
||||
private static final HashSet<String> THROWABLE_SET = new HashSet<>();
|
||||
|
||||
private void reportToServer(final String text) {
|
||||
Thread t = new Thread(() -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
@@ -115,11 +121,11 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
map.put("version", Launcher.VERSION);
|
||||
map.put("log", Logging.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/crash.php"), map);
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://hmcl.huangyuhui.net/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
Logging.LOG.log(Level.SEVERE, "Crash server response: " + response);
|
||||
LOG.log(Level.SEVERE, "Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to post HMCL server.", ex);
|
||||
LOG.log(Level.SEVERE, "Unable to post HMCL server.", ex);
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.i18n;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
|
||||
public final class I18n {
|
||||
|
||||
private I18n() {}
|
||||
|
||||
private static ResourceBundle RESOURCE_BUNDLE = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Settings cannot be initialized", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceBundle getResourceBundle() {
|
||||
return RESOURCE_BUNDLE == null ? Locales.DEFAULT.getResourceBundle() : RESOURCE_BUNDLE;
|
||||
}
|
||||
|
||||
public static String i18n(String key, Object... formatArgs) {
|
||||
return String.format(i18n(key), formatArgs);
|
||||
}
|
||||
|
||||
public static String i18n(String key) {
|
||||
try {
|
||||
return getResourceBundle().getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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
|
||||
@@ -15,9 +15,8 @@
|
||||
* 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.setting;
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.ui.construct.UTF8Control;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
@@ -53,7 +52,7 @@ public final class Locales {
|
||||
*/
|
||||
public static final SupportedLocale RU = new SupportedLocale(new Locale("ru"));
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN);
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH);
|
||||
|
||||
public static SupportedLocale getLocale(int index) {
|
||||
return Lang.get(LOCALES, index).orElse(DEFAULT);
|
||||
+8
-6
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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
|
||||
@@ -15,7 +15,9 @@
|
||||
* 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.ui.construct;
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -26,14 +28,14 @@ import java.util.Locale;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public final class UTF8Control extends ResourceBundle.Control {
|
||||
final class UTF8Control extends ResourceBundle.Control {
|
||||
|
||||
public static final UTF8Control INSTANCE = new UTF8Control();
|
||||
|
||||
private UTF8Control() {}
|
||||
|
||||
@Override
|
||||
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
|
||||
throws IOException {
|
||||
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IOException {
|
||||
// The below is a copy of the default implementation.
|
||||
String bundleName = toBundleName(baseName, locale);
|
||||
String resourceName = toResourceName(bundleName, "properties");
|
||||
@@ -54,7 +56,7 @@ public final class UTF8Control extends ResourceBundle.Control {
|
||||
if (stream != null) {
|
||||
try {
|
||||
// Only this line is changed to make it to read properties files as UTF-8.
|
||||
bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
|
||||
bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8));
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
@@ -919,6 +919,14 @@
|
||||
-fx-stroke-width: 3.0;
|
||||
}
|
||||
|
||||
.small-spinner-pane .jfx-spinner {
|
||||
-jfx-radius: 10;
|
||||
}
|
||||
|
||||
.small-spinner-pane .jfx-spinner > .arc {
|
||||
-fx-stroke-width: 3.0;
|
||||
}
|
||||
|
||||
.second-spinner {
|
||||
-jfx-radius: 30;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
type="StackPane">
|
||||
@@ -56,10 +57,9 @@
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning"/>
|
||||
<StackPane fx:id="acceptPane">
|
||||
<SpinnerPane fx:id="acceptPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="btnAccept" onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
|
||||
<JFXSpinner fx:id="spinnerAccept" styleClass="small-spinner" />
|
||||
</StackPane>
|
||||
</SpinnerPane>
|
||||
<JFXButton onMouseClicked="#onCreationCancel" text="%button.cancel" styleClass="dialog-cancel"/>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
|
||||
<?import java.lang.String?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<JFXSpinner fx:id="spinner" style="-fx-radius:16" styleClass="materialDesign-purple, first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
|
||||
<VBox fx:id="listPane" spacing="10" style="-fx-padding: 20 20 70 20;">
|
||||
@@ -32,11 +32,9 @@
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<JFXTextField fx:id="txtServerIp" promptText="%account.injector.server_ip">
|
||||
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url">
|
||||
<validators>
|
||||
<URLValidator message="%input.url">
|
||||
</URLValidator>
|
||||
<URLValidator message="%input.url.http">
|
||||
<protocols>
|
||||
<String fx:value="http" />
|
||||
<String fx:value="https" />
|
||||
@@ -48,7 +46,9 @@
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
</SpinnerPane>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints />
|
||||
</columnConstraints>
|
||||
<Label text="%account.injector.server_ip" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
|
||||
<Label fx:id="lblServerIp" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
|
||||
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;" GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
|
||||
@@ -78,7 +78,6 @@
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</StackPane>
|
||||
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER" />
|
||||
</StackPane>
|
||||
</JFXDialog>
|
||||
</fx:root>
|
||||
@@ -8,6 +8,8 @@
|
||||
<?import org.jackhuang.hmcl.ui.construct.AdvancedListBox?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
type="StackPane"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
@@ -44,6 +46,9 @@
|
||||
</StackPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
<ImageView fx:id="welcomeView">
|
||||
<Image url="/assets/img/welcome.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
</center>
|
||||
<top>
|
||||
|
||||
@@ -104,8 +104,8 @@
|
||||
</right>
|
||||
</BorderPane>
|
||||
</ComponentList>
|
||||
<HBox alignment="CENTER" style="-fx-padding: 10 0 10 0;">
|
||||
<JFXButton text="%settings.advanced" onMouseClicked="#onShowAdvanced" buttonType="RAISED" styleClass="jfx-button-raised"/>
|
||||
<HBox alignment="CENTER_LEFT" style="-fx-padding: 20 0 12 0;">
|
||||
<Label text="%settings.advanced" style="-fx-text-fill: #616161;" />
|
||||
</HBox>
|
||||
<ComponentList fx:id="advancedSettingsPane" depth="1">
|
||||
<JFXTextField labelFloat="true" promptText="%settings.advanced.jvm_args" styleClass="fit-width"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
@@ -20,9 +20,10 @@ about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=Author
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=Thanks to
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
|
||||
about.dependency=Dependency
|
||||
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors who participated in this project via issues, pull requests, etc.
|
||||
about.dependency=Dependencies
|
||||
# Due to space limitations, only first authors are listed
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
|
||||
about.claim=Statement
|
||||
about.claim.statement=Minecraft is copyrighted by Mojang AB. We are not responsible for any copyright infringement arising from the use of this software.
|
||||
about.open_source=Open Source
|
||||
@@ -31,19 +32,20 @@ about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
account=Accounts
|
||||
account.character=character
|
||||
account.choose=Choose a character
|
||||
account.current=Current
|
||||
account.create=Create a new account
|
||||
account.email=Email
|
||||
account.failed.connect_authentication_server=Cannot connect the authentication server. Check your network.
|
||||
account.failed.invalid_credentials=Incorrect password. Or forbidden to log in temporarily.
|
||||
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your network.
|
||||
account.failed.invalid_credentials=Incorrect password, or you are forbidden to login temporarily.
|
||||
account.failed.invalid_password=Invalid password
|
||||
account.failed.invalid_token=Please log out and re-input your password to log in.
|
||||
account.failed.no_charactor=No character in this account.
|
||||
account.failed.invalid_token=Please log out and re-input your password to login.
|
||||
account.failed.no_character=No character in this account.
|
||||
account.failed.no_selected_server=No authentication server is selected.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
account.injector.add=Add authentication server
|
||||
account.injector.manage=Manage authentication servers
|
||||
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
|
||||
account.injector.server=Auth Server
|
||||
account.injector.server_ip=Server URL
|
||||
account.injector.server_url=Server URL
|
||||
account.injector.server_name=Server Name
|
||||
account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
@@ -59,72 +61,39 @@ archive.game_version=Game
|
||||
archive.version=Version
|
||||
|
||||
assets.download=Download assets
|
||||
assets.download_all=Check for the completion of assets
|
||||
assets.failed=Failed to get the list, try again.
|
||||
assets.failed_download=Failed to download assets, may cause no sounds and language files.
|
||||
assets.download_all=Check the integrity of assets
|
||||
|
||||
button.about=About
|
||||
button.cancel=Cancel
|
||||
button.clear=Clear
|
||||
button.close=Close
|
||||
button.copy=Copy
|
||||
button.delete=Delete
|
||||
button.download=Download
|
||||
button.edit=Edit
|
||||
button.explore=Explore
|
||||
button.info=Info
|
||||
button.install=Install
|
||||
button.more=More
|
||||
button.no=No
|
||||
button.ok=OK
|
||||
button.others=Others
|
||||
button.preview=Preview
|
||||
button.refresh=Refresh
|
||||
button.retry=Retry
|
||||
button.save=Save
|
||||
button.test=Test
|
||||
button.yes=Yes
|
||||
|
||||
color.blue=Blue
|
||||
color.dark_blue=Dark Blue
|
||||
color.green=Green
|
||||
color.orange=Orange
|
||||
color.purple=Purple
|
||||
color.red=Red
|
||||
color.recent=Recommended
|
||||
color.custom=Custom Color
|
||||
|
||||
crash.NoClassDefFound=Please check "HMCL" software is complete.
|
||||
crash.advice.ClassNotFoundException=Minecraft or mods are incomplete. Retry if there are some libraries that have not downloaded or update your game and mods! Or you can try Game Settings -> Manage (Version) -> Delete library files to solve the problem.
|
||||
crash.advice.ConcurrentModificationException=Maybe your Java is newer than 1.8.0_11, you could downgrade to Java 7.
|
||||
crash.advice.LWJGLException=Maybe your video driver does not work well, please update your video driver.
|
||||
crash.advice.NoSuchFieldError=Minecraft or mods are incomplete. Retry if there are some libraries that have not downloaded or update your game and mods!
|
||||
crash.advice.OpenGL=Maybe drivers caused problems.
|
||||
crash.advice.OutOfMemoryError=The maximum memory of JVM is too small, please modify it.
|
||||
crash.advice.SecurityException=Maybe you have modified minecraft.jar but have not removed the META-INF.
|
||||
crash.advice.UnsupportedClassVersionError=Maybe your java is too old, try to update the java.
|
||||
crash.advice.no=No advice.
|
||||
crash.advice.no_lwjgl=Maybe drivers caused problems.
|
||||
crash.advice.otherwise=Maybe mods caused problems.
|
||||
crash.error=Minecraft has crashed.
|
||||
crash.launcher=Launcher has crashed!
|
||||
crash.minecraft=Minecraft has crashed!
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
|
||||
|
||||
download=Download
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
|
||||
download.BMCL=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
download.failed=Failed to download
|
||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||
download.mojang=Mojang
|
||||
download.not_200=Failed to download, the response code is %s.
|
||||
download.source=Download Source
|
||||
download.successfully=Downloaded successfully
|
||||
|
||||
extension.bat=Windows Bat file
|
||||
extension.mod=Mod file
|
||||
extension.png=Image file
|
||||
extension.sh=Bash shell
|
||||
|
||||
fatal.missing_javafx=JavaFX is missing.\nIf you are using Java 11 or later, please downgrade to Java 8 or 10.\nIf you are using OpenJDK, please ensure OpenJFX is included.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use HMCL, but HMCL will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause HMCL not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
|
||||
|
||||
folder.config=Configs
|
||||
folder.coremod=Core Mod
|
||||
folder.game=Game Dir
|
||||
@@ -133,16 +102,16 @@ folder.resourcepacks=Resourcepacks
|
||||
folder.saves=Saves
|
||||
folder.screenshots=Screenshots
|
||||
|
||||
general.prompt.empty=(None)
|
||||
|
||||
input.email=The username must be an e-mail.
|
||||
input.number=Must be a number.
|
||||
input.not_empty=Input Requrired!
|
||||
input.not_empty=Required field
|
||||
input.url=Must be a valid URL.
|
||||
input.url.http=Only Http or Https accepted.
|
||||
|
||||
install=Install New Game
|
||||
install.failed=Failed to install
|
||||
install.installer.choose=Choose a %s version
|
||||
install.installer.failed=Unable to install %s.
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Game
|
||||
install.installer.install=
|
||||
@@ -154,37 +123,29 @@ install.modpack=Install a modpack (CurseForge supported)
|
||||
install.new_game=Install a New Game
|
||||
install.new_game.already_exists=This version has already been existing.
|
||||
install.new_game.current_game_version=Current Game Version
|
||||
install.no_version=The version is not found.
|
||||
install.no_version_if_intall=The required version is not found, do you wish to install the version automatically?
|
||||
install.release_time=Release Time
|
||||
install.select=Select an operation
|
||||
install.success=Installed successfully
|
||||
install.time=Time
|
||||
install.type=Type
|
||||
|
||||
lang=English
|
||||
lang.default=Belong to OS language.
|
||||
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or newer Java version.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
|
||||
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or later Java versions.
|
||||
launch.advice.newer_java=Java 8 is suggested, which can make game run more fluently. And many mods and Minecraft 1.12 and newer versions requires Java 8.
|
||||
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game probably crash. The launcher will try to launch it.
|
||||
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=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.prelaunch_command=Prelaunch command fail.
|
||||
launch.state.dependencies=Dependencies
|
||||
launch.state.done=Done
|
||||
launch.state.logging_in=Logging In
|
||||
launch.state.modpack=Preparing for modpack
|
||||
launch.state.waiting_launching=Waiting for game launching
|
||||
launch.unsupported_launcher_version=Sorry, the launcher cannot launch minecraft, but will retry launching it.
|
||||
launch.wrong_javadir=Incorrect Java directory, will reset to default Java directory.
|
||||
|
||||
launcher=Launcher
|
||||
@@ -196,61 +157,31 @@ launcher.common_directory.choose=Choose common directory.
|
||||
launcher.contact=Contact Us
|
||||
launcher.crash=Hello Minecraft! Launcher has crashed!
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
|
||||
launcher.exit_failed=Failed to shutdown.
|
||||
launcher.modpack=Documentations for modpacks.
|
||||
launcher.restart=Options will be in operations only if restart this app.
|
||||
launcher.tab.about=About
|
||||
launcher.tab.general=General
|
||||
launcher.tab.ui=UI
|
||||
launcher.update_java=Please upgrade your Java.
|
||||
launcher.update_launcher=Check for update
|
||||
launcher.versions_json_not_formatted=The version information of %s is malformed! Redownload it?
|
||||
launcher.versions_json_not_matched=The version %s is malformed! There are a json:%s in this version. Do you want to fix this problem?
|
||||
launcher.versions_json_not_matched_cannot_auto_completion=The version %s lost version information file, delete it?
|
||||
|
||||
login.changed_client_token=The server response has changed the client token.
|
||||
login.choose_charactor=Please choose the character you want
|
||||
login.empty_username=You have not set a username!
|
||||
login.enter_password=Please enter your password.
|
||||
login.enter_username=Please enter your name.
|
||||
login.failed=Failed to login
|
||||
login.invalid_username=Invalid username
|
||||
login.invalid_uuid_and_username=Invalid UUID and username
|
||||
login.no_valid_character=No valid character, please visit skinme.cc and create your own character.
|
||||
|
||||
logwindow.search=Search
|
||||
logwindow.show_lines=Show Lines
|
||||
logwindow.terminate_game=Terminate Game
|
||||
logwindow.title=Log
|
||||
|
||||
main_page=Home
|
||||
|
||||
message.cancelled=Cancelled
|
||||
message.cannot_open_explorer=Cannot open explorer:
|
||||
message.confirm=Confirm
|
||||
message.doing=Please wait
|
||||
message.downloading=Downloading...
|
||||
message.error=Error
|
||||
message.info=Info
|
||||
message.loading=Loading...
|
||||
message.success=Tasks succeeded
|
||||
message.unknown=Unknown
|
||||
|
||||
minecraft.invalid=Invalid
|
||||
minecraft.invalid_jar=Invalid Jar
|
||||
minecraft.modified=(Modified!)
|
||||
minecraft.not_a_file=Not a file
|
||||
minecraft.not_found=Not found
|
||||
minecraft.not_readable=Not readable
|
||||
minecraft.wrong_path=Wrong Minecraft path, the launcher could not find the path.
|
||||
|
||||
modpack=Mod pack
|
||||
modpack.choose=Choose a modpack zip file which you want to import. If you want to update the modpack, please enter the version you want to update.
|
||||
modpack.choose=Choose a modpack zip file which you want to install.
|
||||
modpack.desc=Describe your modpack, including precautions, changlog, supporting Markdown(also supporting online pictures).
|
||||
modpack.enter_name=Enter your desired name for this game.
|
||||
modpack.export=Export Modpack
|
||||
modpack.export_error=Failed to export the modpack, maybe the format of your game directory is incorrect or failed to manage files.
|
||||
modpack.export_finished=Exporting the modpack finished. See
|
||||
modpack.files.blueprints=BuildCraft blueprints
|
||||
modpack.files.config=Mod configs
|
||||
modpack.files.dumps=NEI debug output
|
||||
@@ -263,22 +194,20 @@ modpack.files.resourcepacks=Resource(Texture) packs
|
||||
modpack.files.saves=Saved games
|
||||
modpack.files.scripts=MineTweaker configuration
|
||||
modpack.files.servers_dat=Multiplayer servers list
|
||||
modpack.included_launcher=The modpack is included the launcher, you can publish it directly.
|
||||
modpack.install=Install %s modpack
|
||||
modpack.installing=Installing modpack
|
||||
modpack.introduction=Curse, MultiMC, HMCL, MCBBS modpacks supprted.
|
||||
modpack.invalid=Invalid modpack file.
|
||||
modpack.mismatched_type=Mismatched modpack type, your current game is %s modpack, but your update file is %s modpack.
|
||||
modpack.name=Modpack Name
|
||||
modpack.not_a_valid_location=Not a valid modpack path
|
||||
modpack.not_a_valid_name=Not a valid modpack name
|
||||
modpack.not_included_launcher=You can use the modpack by clicking the "Import Modpack" button.
|
||||
modpack.scan=Scanning this modpack
|
||||
modpack.task.install=Import Modpack
|
||||
modpack.task.install.error=Failed to install the modpack. Maybe the files is incorrect, or a management issue occurred.
|
||||
modpack.task.install.will=Install the modpack:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Install relative files to Curse modpack
|
||||
modpack.type.curse.error=Unable to complete this Curse modpack. Please retry.
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported.
|
||||
@@ -289,7 +218,6 @@ modpack.wizard.step.1.title=Set the basic options to the modpack.
|
||||
modpack.wizard.step.2=Files selection
|
||||
modpack.wizard.step.2.title=Choose the files you want to put into the modpack.
|
||||
modpack.wizard.step.3=Description
|
||||
modpack.wizard.step.3.title=Describe your modpack.
|
||||
modpack.wizard.step.initialization.exported_version=The exported game version
|
||||
modpack.wizard.step.initialization.include_launcher=Include the launcher
|
||||
modpack.wizard.step.initialization.save=Choose a path to export the game files to
|
||||
@@ -300,11 +228,8 @@ mods.add=Add mods
|
||||
mods.add.failed=Failed to add mods %s.
|
||||
mods.add.success=Successfully added mods %s.
|
||||
mods.choose_mod=Choose your mods
|
||||
mods.default_information=Please ensure that you have installed Forge or LiteLoader before installing mods!\nYou can drop your mod files from explorer/finder, and delete mods by the delete button.\nDisable a mod by leaving the check box unchecked; Choose an item to get the information.
|
||||
mods.remove=Remove
|
||||
|
||||
operation.confirm_stop=Terminate the operations?
|
||||
operation.stopped=The operation was aborted.
|
||||
|
||||
profile=Profile
|
||||
profile.default=Current directory
|
||||
@@ -312,8 +237,6 @@ profile.home=User home
|
||||
profile.instance_directory=Game Directory
|
||||
profile.instance_directory.choose=Choose Game Directory
|
||||
profile.new=New Config
|
||||
profile.new_name=New Profile Name:
|
||||
profile.remove=Sure to remove profile %s?
|
||||
profile.title=Game Directories
|
||||
profile.selected=Selected
|
||||
|
||||
@@ -323,7 +246,7 @@ selector.custom=Custom
|
||||
|
||||
settings=Settings
|
||||
|
||||
settings.advanced=Advanced
|
||||
settings.advanced=Advanced Settings
|
||||
settings.advanced.dont_check_game_completeness=Don't check game completeness
|
||||
settings.advanced.game_dir.default=Default (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Independent (.minecraft/versions/<version name>/, except assets,libraries)
|
||||
@@ -342,10 +265,7 @@ settings.advanced.precall_command=Pre-Launch command(will be executed before gam
|
||||
settings.advanced.server_ip=Server Host
|
||||
settings.advanced.wrapper_launcher=Wrapper Launcher(i.e. optirun...)
|
||||
|
||||
settings.cannot_remove_default_config=Cannot remove the default configution.
|
||||
settings.custom=Custom
|
||||
settings.default=Default
|
||||
settings.failed_load=Failed to load settings file. Remove it?
|
||||
|
||||
settings.game=Games
|
||||
settings.game.dimension=Game Window Dimension
|
||||
@@ -378,16 +298,11 @@ settings.launcher.theme=Theme
|
||||
|
||||
settings.max_memory=Max Memory/MB
|
||||
settings.physical_memory=Physical Memory Size
|
||||
settings.run_directory=Run Directory(Version Isolation)
|
||||
settings.show_log=Show Logs
|
||||
settings.tabs.assets_downloads=Assets
|
||||
settings.tabs.game_download=Games
|
||||
settings.tabs.installers=Installers
|
||||
settings.test_game=Test game
|
||||
settings.type.global=Global version settings(all shared)
|
||||
settings.type=Version setting type
|
||||
settings.type.special=Specialized version settings(will not affect other versions)
|
||||
settings.update_version=Update version json.
|
||||
|
||||
update=Update
|
||||
update.failed=Failed to check for updates.
|
||||
@@ -395,7 +310,6 @@ update.found=Found Update!
|
||||
update.newest_version=Newest version: %s
|
||||
update.latest=This is latest Version.
|
||||
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
|
||||
update.should_open_link=Are you willing to update the launcher?
|
||||
update.tooltip=Update
|
||||
|
||||
version.cannot_read=Unable to gather the game version. Cannot continue auto-installing.
|
||||
@@ -409,7 +323,6 @@ 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
|
||||
version.manage.redownload_json=Redownload Minecraft Configuration(minecraft.json)
|
||||
version.manage.remove=Delete this version
|
||||
version.manage.remove.confirm=Sure to remove version %s?
|
||||
version.manage.remove_libraries=Delete library files
|
||||
@@ -419,10 +332,6 @@ version.settings=Settings
|
||||
version.update=Update
|
||||
|
||||
wizard.prev=< Prev
|
||||
wizard.close=Close
|
||||
wizard.failed=Failed
|
||||
wizard.finish=Finish
|
||||
wizard.help=Help
|
||||
wizard.next=Next >
|
||||
wizard.steps=Steps
|
||||
wizard.summary=Summary
|
||||
@@ -0,0 +1,337 @@
|
||||
# Hello Minecraft! Launcher.
|
||||
# Copyright (C) 2013 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/}.
|
||||
#
|
||||
#author: huangyuhui
|
||||
about.copyright=版權
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鳴謝
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默認背景圖)\n所有通過 Issues、Pull Requests 等方式參與本項目的貢獻者
|
||||
about.dependency=依賴
|
||||
# 由於篇幅限制,僅列出第一作者
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
|
||||
about.claim=免責聲明
|
||||
about.claim.statement=Minecraft 版權歸 Mojang AB 所有,使用本軟件產生的版權問題,軟件製作方概不負責。請支持正版。
|
||||
about.open_source=開源
|
||||
about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
|
||||
account=賬戶
|
||||
account.character=角色
|
||||
account.choose=選擇一個角色
|
||||
account.create=新建賬戶
|
||||
account.email=郵箱
|
||||
account.failed.connect_authentication_server=無法連接認證服務器,可能是網絡問題
|
||||
account.failed.invalid_credentials=您的用戶名或密碼錯誤,或者登錄次數過多被暫時禁止登錄,請稍後再試
|
||||
account.failed.invalid_password=無效的密碼
|
||||
account.failed.invalid_token=請嘗試登出並重新輸入密碼登錄
|
||||
account.failed.no_character=該帳號沒有角色
|
||||
account.failed.no_selected_server=未選擇認證服務器
|
||||
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
|
||||
account.injector.add=添加認證服務器
|
||||
account.injector.manage=管理認證服務器
|
||||
account.injector.http=警告:此服務器使用不安全的 HTTP 協議,您的密碼在登錄時會被明文傳輸。
|
||||
account.injector.server=認證服務器
|
||||
account.injector.server_url=服務器地址
|
||||
account.injector.server_name=服務器名稱
|
||||
account.methods=登錄方式
|
||||
account.methods.authlib_injector=authlib-injector 登錄
|
||||
account.methods.no_method=沒有登入方式
|
||||
account.methods.offline=離線模式
|
||||
account.methods.yggdrasil=正版登錄
|
||||
account.missing=沒有賬戶
|
||||
account.password=密碼
|
||||
account.username=用戶名
|
||||
|
||||
archive.author=作者
|
||||
archive.game_version=遊戲版本
|
||||
archive.version=版本
|
||||
|
||||
assets.download=下載資源
|
||||
assets.download_all=檢查資源文件完整性
|
||||
|
||||
button.cancel=取消
|
||||
button.clear=清除
|
||||
button.delete=刪除
|
||||
button.edit=修改
|
||||
button.install=安裝
|
||||
button.no=否
|
||||
button.ok=確定
|
||||
button.refresh=刷新
|
||||
button.save=保存
|
||||
button.yes=是
|
||||
|
||||
color.recent=推薦
|
||||
color.custom=自定義顏色
|
||||
|
||||
crash.NoClassDefFound=請確認 HMCL 本體是否完整,或更新您的 Java。
|
||||
crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟件崩潰,請檢查您的 Java 環境或您的電腦!可以嘗試重新安裝 Java。
|
||||
|
||||
download=下載
|
||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.failed=下載失敗
|
||||
download.failed.refresh=加載版本列表失敗,點擊此處重試。
|
||||
download.mojang=官方
|
||||
|
||||
extension.bat=Windows 腳本
|
||||
extension.mod=模組文件
|
||||
extension.png=圖片文件
|
||||
extension.sh=Bash 腳本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=當前 Java 平台缺少 DST Root CA X3 證書。\n您依然可以使用 HMCL,但將無法連接到部分站點(如使用 Let's Encrypt 證書的站點),這可能會使 HMCL 無法正常工作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
|
||||
folder.config=配置文件夾
|
||||
folder.coremod=核心MOD文件夾
|
||||
folder.game=遊戲文件夾
|
||||
folder.mod=MOD文件夾
|
||||
folder.resourcepacks=資源包文件夾
|
||||
folder.saves=存檔文件夾
|
||||
folder.screenshots=截圖文件夾
|
||||
|
||||
general.prompt.empty=(無)
|
||||
|
||||
input.email=用戶名必須是郵箱
|
||||
input.number=必須是數字
|
||||
input.not_empty=必填項
|
||||
input.url=必須是合法的鏈接
|
||||
|
||||
install=添加遊戲
|
||||
install.failed=安裝失敗
|
||||
install.installer.choose=選擇 %s 版本
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=遊戲
|
||||
install.installer.install=安裝 %s
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=暫不安裝 %s,可以點擊此處安裝
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s 版本
|
||||
install.modpack=安裝整合包
|
||||
install.new_game=安裝新遊戲版本
|
||||
install.new_game.already_exists=此版本已經存在,請換一個名字
|
||||
install.new_game.current_game_version=當前遊戲版本
|
||||
install.select=請選擇安裝方式
|
||||
install.success=安裝成功
|
||||
|
||||
lang=繁體中文
|
||||
lang.default=跟隨系統語言
|
||||
|
||||
launch.advice.different_platform=您的系統是 64 位的但是 Java 是 32 位的,推薦您安裝 64 位 Java。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,請使用 Java 8 或最新版本。
|
||||
launch.advice.java9=不高於 1.13 的安裝了 Mod 的 Minecraft 不支持 Java 9 或更高版本,請使用 Java 8。
|
||||
launch.advice.newer_java=檢測到您未使用 Java 8 及更新版本,Java 8 能使遊戲更流暢而且 Minecraft 1.12 及更新版本和很多 Mod 強制需要需要 Java 8。
|
||||
launch.advice.not_enough_space=您設置的內存大小過大,由於超過了系統內存大小 %dMB,所以可能影響遊戲體驗或無法啓動遊戲,啓動器仍會嘗試啓動。
|
||||
launch.advice.too_large_memory_for_32bit=您設置的內存大小過大,由於可能超過了 32 位 Java 的內存分配限制,所以可能無法啓動遊戲,請將內存調至 1024MB 或更小,啓動器仍會嘗試啓動。
|
||||
launch.failed=啓動失敗
|
||||
launch.failed.cannot_create_jvm=截獲到無法創建 Java 虛擬機,可能是 Java 參數有問題,可以在設置中開啓無參數模式啓動。
|
||||
launch.failed.creating_process=啓動失敗,在創建新進程時發生錯誤,可能是 Java 路徑錯誤。
|
||||
launch.failed.decompressing_natives=未能解壓遊戲本地庫。
|
||||
launch.failed.executable_permission=未能爲啓動文件添加執行權限。
|
||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看日志文件,或聯繫他人尋求幫助。
|
||||
launch.state.dependencies=正在處理遊戲依賴
|
||||
launch.state.done=啓動完成
|
||||
launch.state.logging_in=登錄中
|
||||
launch.state.modpack=正在下載必要文件
|
||||
launch.state.waiting_launching=等待遊戲啓動
|
||||
launch.wrong_javadir=錯誤的 Java 路徑,將自動重置爲默認 Java 路徑。
|
||||
|
||||
launcher=啓動器
|
||||
launcher.background=背景地址
|
||||
launcher.background.choose=選擇背景路徑
|
||||
launcher.background.default=默認(自動檢索啓動器同目錄下的 background.png/jpg 及 bg 文件夾內的圖片)
|
||||
launcher.common_directory=公共文件夾
|
||||
launcher.common_directory.choose=選擇公共文件夾
|
||||
launcher.contact=聯繫我們
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並通過 MCBBS、貼吧、GitHub 或 Minecraft Forum 反饋 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已檢測到您的啓動器不是最新版本,請更新後再試!
|
||||
launcher.update_java=請更新您的 Java
|
||||
|
||||
login.empty_username=你還未設置用戶名!
|
||||
login.enter_password=請輸入您的密碼
|
||||
|
||||
logwindow.show_lines=顯示行數
|
||||
logwindow.terminate_game=結束遊戲進程
|
||||
logwindow.title=日誌
|
||||
|
||||
main_page=主頁
|
||||
|
||||
message.confirm=提示
|
||||
message.doing=請耐心等待
|
||||
message.downloading=正在下載...
|
||||
message.error=錯誤
|
||||
message.info=提示
|
||||
message.success=已完成
|
||||
message.unknown=未知
|
||||
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=選擇要安裝的遊戲整合包文件
|
||||
modpack.desc=描述你要製作的整合包,比如整合包注意事項和更新記錄,支持 Markdown(圖片請用網絡圖)。
|
||||
modpack.enter_name=給遊戲起個你喜歡的名字
|
||||
modpack.export=導出整合包
|
||||
modpack.files.blueprints=BuildCraft 藍圖
|
||||
modpack.files.config=Mod 配置文件
|
||||
modpack.files.dumps=NEI 調試輸出
|
||||
modpack.files.liteconfig=Mod 配置文件
|
||||
modpack.files.mods=Mod
|
||||
modpack.files.mods.voxelmods=VoxelMods 配置,如小地圖
|
||||
modpack.files.options_txt=遊戲設定
|
||||
modpack.files.optionsshaders_txt=光影設定
|
||||
modpack.files.resourcepacks=資源包(材質包)
|
||||
modpack.files.saves=遊戲存檔
|
||||
modpack.files.scripts=MineTweaker 配置
|
||||
modpack.files.servers_dat=多人遊戲服務器列表
|
||||
modpack.install=安裝%s整合包
|
||||
modpack.installing=正在安裝整合包
|
||||
modpack.introduction=支持 Curse、MultiMC、HMCL、MCBBS 整合包。
|
||||
modpack.invalid=無效的整合包升級文件,可能是下載時出現問題。
|
||||
modpack.mismatched_type=整合包類型不匹配,當前遊戲是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。
|
||||
modpack.name=整合包名稱
|
||||
modpack.not_a_valid_name=不是一個有效的整合包名稱
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=導入整合包
|
||||
modpack.task.install.error=安裝失敗,可能是整合包格式不正確或操作文件失敗。
|
||||
modpack.task.install.will=將會安裝整合包:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=下載 Curse 整合包相關文件
|
||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=該整合包不被支持。僅 HMCL、MultiMC、Curse 整合包受支持。
|
||||
modpack.update=正在升級整合包
|
||||
modpack.wizard=導出整合包嚮導
|
||||
modpack.wizard.step.1=基本設置
|
||||
modpack.wizard.step.1.title=設置整合包的主要信息
|
||||
modpack.wizard.step.2=文件選擇
|
||||
modpack.wizard.step.2.title=選中你想加到整合包中的文件或文件夾
|
||||
modpack.wizard.step.3=整合包描述
|
||||
modpack.wizard.step.initialization.exported_version=要導出的遊戲版本
|
||||
modpack.wizard.step.initialization.include_launcher=包含啓動器
|
||||
modpack.wizard.step.initialization.save=選擇要導出到的遊戲整合包位置
|
||||
modpack.wizard.step.initialization.warning=在製作整合包前,請您確認您選擇的版本可以正常啓動,\n並保證您的 Minecraft 是正式版而非快照版,\n而且不應當將不允許非官方途徑傳播的 Mod、材質包等納入整合包。\n整合包會保存您目前的下載源設置
|
||||
|
||||
mods=模組管理
|
||||
mods.add=添加模組
|
||||
mods.add.failed=添加模組 %s 失敗。
|
||||
mods.add.success=成功添加模組 %s。
|
||||
mods.choose_mod=選擇模組
|
||||
mods.remove=刪除
|
||||
|
||||
|
||||
profile=配置
|
||||
profile.default=當前目錄
|
||||
profile.home=主文件夾
|
||||
profile.instance_directory=遊戲路徑
|
||||
profile.instance_directory.choose=選擇遊戲路徑
|
||||
profile.new=新建配置
|
||||
profile.title=遊戲目錄
|
||||
profile.selected=已選中
|
||||
|
||||
selector.choose=選擇
|
||||
selector.choose_file=選擇文件
|
||||
selector.custom=自定義
|
||||
|
||||
settings=普通設置
|
||||
|
||||
settings.advanced=高級設置
|
||||
settings.advanced.dont_check_game_completeness=不檢查遊戲完整性
|
||||
settings.advanced.game_dir.default=默認(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本獨立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_args_default=啓動器默認添加的參數(請不要重複添加):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=內存永久保存區域(不必填寫,MB)
|
||||
settings.advanced.jvm_args=Java 虛擬機參數(不必填寫)
|
||||
settings.advanced.launcher_visibility.close=遊戲啓動後結束啓動器
|
||||
settings.advanced.launcher_visibility.hide=遊戲啓動後隱藏啓動器
|
||||
settings.advanced.launcher_visibility.hide_reopen=隱藏啓動器並在遊戲結束後重新打開
|
||||
settings.advanced.launcher_visibility.keep=保持啓動器可見
|
||||
settings.advanced.launcher_visible=啓動器可見性
|
||||
settings.advanced.minecraft_arguments=Minecraft 額外參數(不必填寫)
|
||||
settings.advanced.no_common=不使用公共路徑
|
||||
settings.advanced.no_jvm_args=不添加默認的 JVM 參數(使用 Java 9 時必勾)
|
||||
settings.advanced.precall_command=啓動前執行命令(不必填寫,將在遊戲啓動前調用)
|
||||
settings.advanced.server_ip=直入服務器 IP 地址(不必填寫,啓動遊戲後直接進入對應服務器)
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填寫,如 optirun)
|
||||
|
||||
settings.custom=自定義
|
||||
|
||||
settings.game=遊戲設置
|
||||
settings.game.dimension=遊戲窗口分辨率
|
||||
settings.game.exploration=瀏覽
|
||||
settings.game.fullscreen=全屏
|
||||
settings.game.game_directory=遊戲路徑(版本隔離)
|
||||
settings.game.game_directory.choose=選擇遊戲路徑
|
||||
settings.game.java_directory=Java 路徑
|
||||
settings.game.java_directory.choose=選擇 Java 路徑
|
||||
settings.game.management=管理
|
||||
|
||||
settings.icon=遊戲圖標
|
||||
|
||||
settings.launcher=啓動器設置
|
||||
settings.launcher.common_path.tooltip=啓動器將所有遊戲資源及依賴庫文件放於此集中管理,如果遊戲文件夾內有現成的將不會使用公共庫文件
|
||||
settings.launcher.download_source=下載源
|
||||
settings.launcher.language=語言
|
||||
settings.launcher.log_font=日誌字體
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=代理賬戶
|
||||
settings.launcher.proxy.no_proxy=直連
|
||||
settings.launcher.proxy.has_proxy=啓用驗證
|
||||
settings.launcher.proxy.host=主機
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密碼
|
||||
settings.launcher.proxy.port=端口
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=賬戶
|
||||
settings.launcher.theme=主題
|
||||
|
||||
settings.max_memory=最大內存(MB)
|
||||
settings.physical_memory=物理內存大小
|
||||
settings.show_log=查看日誌
|
||||
settings.tabs.installers=自動安裝
|
||||
settings.type.global=全局版本設置(使用該設置的版本共用一套設定)
|
||||
settings.type=版本設置類型
|
||||
settings.type.special=單獨版本設置(不會影響到其他版本的設定)
|
||||
|
||||
update=啓動器更新
|
||||
update.failed=檢查更新失敗
|
||||
update.found=發現更新
|
||||
update.newest_version=最新版本爲:%s
|
||||
update.latest=當前版本爲最新版本
|
||||
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼板了,您可以手動粘貼網址打開頁面
|
||||
update.tooltip=更新
|
||||
|
||||
version.cannot_read=讀取遊戲版本失敗,無法進行自動安裝
|
||||
version.forbidden_name=此版本名稱不受支持,請換一個名字
|
||||
version.game.old=遠古版
|
||||
version.game.release=穩定版
|
||||
version.game.snapshot=測試版
|
||||
version.launch=啓動遊戲
|
||||
version.launch_script=生成啓動腳本
|
||||
version.launch_script.failed=生成啓動腳本失敗
|
||||
version.launch_script.save=保存啓動腳本
|
||||
version.launch_script.success=啓動腳本已生成完畢:%s
|
||||
version.manage.redownload_assets_index=重新下載資源配置(assets_index.json)
|
||||
version.manage.remove=刪除該版本
|
||||
version.manage.remove.confirm=真的要刪除版本 %s 嗎?
|
||||
version.manage.remove_libraries=刪除所有庫文件
|
||||
version.manage.rename=重命名該版本
|
||||
version.manage.rename.message=請輸入要改成的名字
|
||||
version.settings=遊戲設置
|
||||
version.update=更新
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失敗
|
||||
wizard.finish=完成
|
||||
wizard.next=下一步 >
|
||||
@@ -20,9 +20,10 @@ about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鸣谢
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有通过 Issues、Pull Requests 等方式参与本项目的贡献者
|
||||
about.dependency=依赖
|
||||
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
|
||||
# 由于篇幅限制,仅列出第一作者
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
|
||||
about.claim=免责声明
|
||||
about.claim.statement=Minecraft 版权归 Mojang AB 所有,使用本软件产生的版权问题,软件制作方概不负责。请支持正版。
|
||||
about.open_source=开源
|
||||
@@ -31,19 +32,20 @@ about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
account=账户
|
||||
account.character=角色
|
||||
account.choose=选择一个角色
|
||||
account.current=当前账户
|
||||
account.create=新建账户
|
||||
account.email=邮箱
|
||||
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
|
||||
account.failed.invalid_credentials=您的用户名或密码错误,或者登录次数过多被暂时禁止登录,请稍后再试
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.no_charactor=该帐号没有角色
|
||||
account.failed.no_character=该帐号没有角色
|
||||
account.failed.no_selected_server=未选择认证服务器
|
||||
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
|
||||
account.injector.add=添加认证服务器
|
||||
account.injector.manage=管理认证服务器
|
||||
account.injector.http=警告:此服务器使用不安全的http协议,您的密码在登录时会被明文传输。
|
||||
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
|
||||
account.injector.server=认证服务器
|
||||
account.injector.server_ip=服务器地址
|
||||
account.injector.server_url=服务器地址
|
||||
account.injector.server_name=服务器名称
|
||||
account.methods=登录方式
|
||||
account.methods.authlib_injector=authlib-injector 登录
|
||||
@@ -60,71 +62,38 @@ archive.version=版本
|
||||
|
||||
assets.download=下载资源
|
||||
assets.download_all=检查资源文件完整性
|
||||
assets.failed=获取列表失败,请刷新重试。
|
||||
assets.failed_download=下载资源文件失败,可能导致没有中文和声音。
|
||||
|
||||
button.about=关于
|
||||
button.cancel=取消
|
||||
button.clear=清除
|
||||
button.close=关闭
|
||||
button.copy=复制
|
||||
button.delete=删除
|
||||
button.download=下载
|
||||
button.edit=修改
|
||||
button.explore=浏览
|
||||
button.info=信息
|
||||
button.install=安装
|
||||
button.more=更多
|
||||
button.no=否
|
||||
button.ok=确定
|
||||
button.others=其他
|
||||
button.preview=预览
|
||||
button.refresh=刷新
|
||||
button.retry=重试
|
||||
button.save=保存
|
||||
button.test=测试
|
||||
button.yes=是
|
||||
|
||||
color.blue=蓝色
|
||||
color.dark_blue=深蓝色
|
||||
color.green=绿色
|
||||
color.orange=橙色
|
||||
color.purple=紫色
|
||||
color.red=红色
|
||||
color.recent=推荐
|
||||
color.custom=自定义颜色
|
||||
|
||||
crash.NoClassDefFound=请确认HMCL本体是否完整,或更新您的Java。
|
||||
crash.advice.ClassNotFoundException=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题,另可尝试游戏设置->(版本)管理->删除库文件解决问题
|
||||
crash.advice.ConcurrentModificationException=这可能是因为您的Java版本高于Java 1.8.0_11导致的,可以尝试卸载Java8安装Java7。
|
||||
crash.advice.LWJGLException=您的电脑不正常,可能需要使用驱动精灵或其他安装器更新显卡驱动。
|
||||
crash.advice.NoSuchFieldError=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题。
|
||||
crash.advice.OpenGL=可能是显卡/声卡驱动问题,也可能是Mod导致的问题。
|
||||
crash.advice.OutOfMemoryError=内存溢出,您设置的Minecraft最大内存过小,请调大!
|
||||
crash.advice.SecurityException=可能是您修改了minecraft.jar但未删除META-INF文件夹的原因。请通过压缩软件删除jar中的META-INF文件夹。
|
||||
crash.advice.UnsupportedClassVersionError=这可能是因为您的Java版本过于老旧,可以尝试更换最新Java并在版本设置的Java路径中设置.
|
||||
crash.advice.no=无建议。
|
||||
crash.advice.no_lwjgl=可能是游戏依赖库不完整或解压依赖库时出错。可以通过下载整合包解决问题。
|
||||
crash.advice.otherwise=可能是Mod或其他问题。
|
||||
crash.error=您的Minecraft崩溃了。
|
||||
crash.launcher=启动器崩溃了!
|
||||
crash.minecraft=Minecraft崩溃了!请认真阅读建议。
|
||||
crash.user_fault=您的系统或Java环境可能安装不当导致本软件崩溃,请检查您的Java环境或您的电脑!可以尝试重新安装Java。
|
||||
crash.NoClassDefFound=请确认 HMCL 本体是否完整,或更新您的 Java。
|
||||
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
||||
|
||||
download=下载
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
|
||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.failed=下载失败
|
||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||
download.mojang=官方
|
||||
download.not_200=下载失败,HTTP状态码:%s.
|
||||
download.source=下载源
|
||||
download.successfully=下载完成
|
||||
|
||||
extension.bat=Windows 脚本
|
||||
extension.mod=模组文件
|
||||
extension.png=图片文件
|
||||
extension.sh=Bash 脚本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Let's Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
|
||||
folder.config=配置文件夹
|
||||
folder.coremod=核心MOD文件夹
|
||||
folder.game=游戏文件夹
|
||||
@@ -133,178 +102,134 @@ folder.resourcepacks=资源包文件夹
|
||||
folder.saves=存档文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
|
||||
general.prompt.empty=(无)
|
||||
|
||||
input.email=用户名必须是邮箱
|
||||
input.number=必须是数字
|
||||
input.not_empty=必填项
|
||||
input.url=必须是合法的链接
|
||||
input.url.http=只支持Http或Https协议
|
||||
|
||||
install=添加游戏
|
||||
install.failed=安装失败
|
||||
install.installer.choose=选择%s版本
|
||||
install.installer.failed=未能成功安装%s.
|
||||
install.installer.choose=选择 %s 版本
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=游戏
|
||||
install.installer.install=安装%s
|
||||
install.installer.install=安装 %s
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=暂不安装%s,可以点击此处安装
|
||||
install.installer.not_installed=暂不安装 %s,可以点击此处安装
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s版本
|
||||
install.installer.version=%s 版本
|
||||
install.modpack=安装整合包
|
||||
install.new_game=安装新游戏版本
|
||||
install.new_game.already_exists=此版本已经存在,请换一个名字
|
||||
install.new_game.current_game_version=当前游戏版本
|
||||
install.no_version=未找到要安装的对应MC版本
|
||||
install.no_version_if_intall=未找到要安装的对应MC版本,是否自动安装需要的MC版本?
|
||||
install.release_time=发布时间
|
||||
install.select=请选择安装方式
|
||||
install.success=安装成功
|
||||
install.time=时间
|
||||
install.type=类型
|
||||
|
||||
lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice.different_platform=您的系统是64位的但是Java是32位的,推荐您安装64位Java.
|
||||
launch.advice.java9=不高于1.13的Minecraft不支持Java 9或更高版本,请使用Java 8.
|
||||
launch.advice.newer_java=检测到您未使用Java 8及更新版本,Java 8能使游戏更流畅而且Minecraft 1.12及更新版本和很多Mod强制需要需要Java 8。
|
||||
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小%dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了32位Java的内存分配限制,所以可能无法启动游戏,请将内存调至1024MB或更小,启动器仍会尝试启动。
|
||||
launch.circular_dependency_versions=发现游戏版本循环引用,请确认您的客户端未被修改或修改导致出现此问题。
|
||||
launch.advice.different_platform=您的系统是 64 位的但是 Java 是 32 位的,推荐您安装 64 位 Java。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
|
||||
launch.advice.java9=不高于 1.13 的安装了 Mod 的 Minecraft 不支持 Java 9 或更高版本,请使用 Java 8。
|
||||
launch.advice.newer_java=检测到您未使用 Java 8 及更新版本,Java 8 能使游戏更流畅而且 Minecraft 1.12 及更新版本和很多 Mod 强制需要需要 Java 8。
|
||||
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小,启动器仍会尝试启动。
|
||||
launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建Java虚拟机,可能是Java参数有问题,可以在设置中开启无参数模式启动
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是Java路径错误。
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
launch.failed.decompressing_natives=未能解压游戏本地库。
|
||||
launch.failed.downloading_libraries=未完成游戏依赖库的下载,还要继续启动游戏吗?
|
||||
launch.failed.executable_permission=未能为启动文件添加执行权限
|
||||
launch.failed.executable_permission=未能为启动文件添加执行权限。
|
||||
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
|
||||
launch.failed.prelaunch_command=启动前执行命令执行失败。
|
||||
launch.state.dependencies=正在处理游戏依赖
|
||||
launch.state.done=启动完成
|
||||
launch.state.logging_in=登录中
|
||||
launch.state.modpack=正在下载必要文件
|
||||
launch.state.waiting_launching=等待游戏启动
|
||||
launch.unsupported_launcher_version=对不起,本启动器现在可能不能启动这个版本的Minecraft,但启动器还是会尝试启动,请尽快将此问题报告给作者。
|
||||
launch.wrong_javadir=错误的Java路径,将自动重置为默认Java路径。
|
||||
launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。
|
||||
|
||||
launcher=启动器
|
||||
launcher.background=背景地址
|
||||
launcher.background.choose=选择背景路径
|
||||
launcher.background.default=默认(自动检索启动器同目录下的background.png/jpg及bg文件夹内的图片)
|
||||
launcher.background.default=默认(自动检索启动器同目录下的 background.png/jpg 及 bg 文件夹内的图片)
|
||||
launcher.common_directory=公共文件夹
|
||||
launcher.common_directory.choose=选择公共文件夹
|
||||
launcher.contact=联系我们
|
||||
launcher.crash=Hello Minecraft!遇到了无法处理的错误,请复制下列内容并通过mcbbs、贴吧、Github或Minecraft Forum反馈bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
launcher.exit_failed=强制退出失败,可能是Forge 1.7.10及更高版本导致的,无法解决。
|
||||
launcher.modpack=整合包作者帮助
|
||||
launcher.restart=本界面选项需要重启本启动器生效
|
||||
launcher.tab.about=关于
|
||||
launcher.tab.general=通用
|
||||
launcher.tab.ui=界面
|
||||
launcher.update_java=请更新您的Java
|
||||
launcher.update_launcher=检查更新
|
||||
launcher.versions_json_not_formatted=版本%s信息文件格式错误,是否重新下载?
|
||||
launcher.versions_json_not_matched=版本%s格式不规范!该版本文件夹下有json:%s,是否更名这个文件来规范格式?
|
||||
launcher.versions_json_not_matched_cannot_auto_completion=版本%s缺失必要的版本信息文件,是否删除该版本?
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并通过 MCBBS、贴吧、GitHub 或 Minecraft Forum 反馈 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
launcher.update_java=请更新您的 Java
|
||||
|
||||
login.changed_client_token=服务器回应已经修改客户端令牌
|
||||
login.choose_charactor=请选择您要使用的角色
|
||||
login.empty_username=你还未设置用户名!
|
||||
login.enter_password=请输入您的密码
|
||||
login.enter_username=请输入您的账号
|
||||
login.failed=登录失败:
|
||||
login.invalid_username=无效的用户名
|
||||
login.invalid_uuid_and_username=无效的UUID和用户名
|
||||
login.no_valid_character=无有效的角色,自行到skinme.cc登陆并创建角色
|
||||
|
||||
logwindow.search=查找
|
||||
logwindow.show_lines=显示行数
|
||||
logwindow.terminate_game=结束游戏进程
|
||||
logwindow.title=日志
|
||||
|
||||
main_page=主页
|
||||
|
||||
message.cancelled=已取消
|
||||
message.cannot_open_explorer=无法打开文件管理器:
|
||||
message.confirm=提示
|
||||
message.doing=请耐心等待
|
||||
message.downloading=正在下载...
|
||||
message.error=错误
|
||||
message.info=提示
|
||||
message.loading=加载中...
|
||||
message.success=已完成
|
||||
message.unknown=未知
|
||||
|
||||
minecraft.invalid=无效的
|
||||
minecraft.invalid_jar=无效的jar包
|
||||
minecraft.modified=(修改的!)
|
||||
minecraft.not_a_file=不是文件
|
||||
minecraft.not_found=找不到minecraft.jar
|
||||
minecraft.not_readable=minecraft.jar不可读
|
||||
minecraft.wrong_path=错误的Minecraft路径,启动器未找到设定的Minecraft路径,请检查。
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=选择要导入的游戏整合包文件,如果您希望更新整合包,请输入要更新的版本名
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持Markdown(图片请用网络图片)。
|
||||
modpack.choose=选择要安装的游戏整合包文件
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown(图片请用网络图)。
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
modpack.export_error=导出失败,可能是您的游戏文件夹格式不正确或操作文件失败
|
||||
modpack.export_finished=整合包导出完成,参见
|
||||
modpack.files.blueprints=BuildCraft蓝图
|
||||
modpack.files.config=Mod配置文件
|
||||
modpack.files.dumps=NEI调试输出
|
||||
modpack.files.liteconfig=Mod配置文件
|
||||
modpack.files.blueprints=BuildCraft 蓝图
|
||||
modpack.files.config=Mod 配置文件
|
||||
modpack.files.dumps=NEI 调试输出
|
||||
modpack.files.liteconfig=Mod 配置文件
|
||||
modpack.files.mods=Mod
|
||||
modpack.files.mods.voxelmods=VoxelMods配置,如小地图
|
||||
modpack.files.mods.voxelmods=VoxelMods 配置,如小地图
|
||||
modpack.files.options_txt=游戏设定
|
||||
modpack.files.optionsshaders_txt=光影设定
|
||||
modpack.files.resourcepacks=资源包(材质包)
|
||||
modpack.files.saves=游戏存档
|
||||
modpack.files.scripts=MineTweaker配置
|
||||
modpack.files.scripts=MineTweaker 配置
|
||||
modpack.files.servers_dat=多人游戏服务器列表
|
||||
modpack.included_launcher=整合包已包含启动器,可直接发布
|
||||
modpack.install=安装%s整合包
|
||||
modpack.installing=正在安装整合包
|
||||
modpack.introduction=支持 Curse, MultiMC, HMCL, MCBBS 整合包。
|
||||
modpack.introduction=支持 Curse、MultiMC、HMCL、MCBBS 整合包。
|
||||
modpack.invalid=无效的整合包升级文件,可能是下载时出现问题。
|
||||
modpack.mismatched_type=整合包类型不匹配,当前游戏是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。
|
||||
modpack.name=整合包名称
|
||||
modpack.not_a_valid_location=不是一个有效整合包位置
|
||||
modpack.not_a_valid_name=不是一个有效的整合包名称
|
||||
modpack.not_included_launcher=整合包未包含启动器,可使用本软件的导入整合包功能导入整合包
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=导入整合包
|
||||
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败
|
||||
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败。
|
||||
modpack.task.install.will=将会安装整合包:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=下载Curse整合包相关文件
|
||||
modpack.type.curse.completion=下载 Curse 整合包相关文件
|
||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=该整合包不被支持。仅HMCL、MultiMC、Curse整合包受支持。
|
||||
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
|
||||
modpack.update=正在升级整合包
|
||||
modpack.wizard=导出整合包向导
|
||||
modpack.wizard.step.1=基本设置
|
||||
modpack.wizard.step.1.title=设置整合包的主要信息
|
||||
modpack.wizard.step.2=文件选择
|
||||
modpack.wizard.step.2.title=选中你想加到整合包中的文件(夹)
|
||||
modpack.wizard.step.2.title=选中你想加到整合包中的文件或文件夹
|
||||
modpack.wizard.step.3=整合包描述
|
||||
modpack.wizard.step.3.title=描述你要制作的整合包
|
||||
modpack.wizard.step.initialization.exported_version=要导出的游戏版本
|
||||
modpack.wizard.step.initialization.include_launcher=包含启动器
|
||||
modpack.wizard.step.initialization.save=选择要导出到的游戏整合包位置
|
||||
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的Minecraft是正式版而非快照版,\n而且不应当将不允许非官方途径传播的Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
|
||||
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的 Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
|
||||
|
||||
mods=模组管理
|
||||
mods.add=添加模组
|
||||
mods.add.failed=添加模组%s失败。
|
||||
mods.add.success=成功添加模组%s。
|
||||
mods.add.failed=添加模组 %s 失败。
|
||||
mods.add.success=成功添加模组 %s。
|
||||
mods.choose_mod=选择模组
|
||||
mods.default_information=安装Mod前你需要确保已安装Forge或LiteLoader!\n您可以从资源管理器拖动mod文件到列表中来添加mod,同时使用删除键可快速删除选中mod\n点掉mod前面的勾可禁用mod,不会加载;选择mod可以获取mod信息
|
||||
mods.remove=删除
|
||||
|
||||
operation.confirm_stop=真的要终止操作吗?
|
||||
operation.stopped=操作被强行终止
|
||||
|
||||
profile=配置
|
||||
profile.default=当前目录
|
||||
@@ -312,8 +237,6 @@ profile.home=主文件夹
|
||||
profile.instance_directory=游戏路径
|
||||
profile.instance_directory.choose=选择游戏路径
|
||||
profile.new=新建配置
|
||||
profile.new_name=新配置名:
|
||||
profile.remove=真的要删除配置%s吗?
|
||||
profile.title=游戏目录
|
||||
profile.selected=已选中
|
||||
|
||||
@@ -325,27 +248,24 @@ settings=普通设置
|
||||
|
||||
settings.advanced=高级设置
|
||||
settings.advanced.dont_check_game_completeness=不检查游戏完整性
|
||||
settings.advanced.game_dir.default=默认(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除assets,libraries)
|
||||
settings.advanced.game_dir.default=默认(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_args_default=启动器默认添加的参数(请不要重复添加):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,MB)
|
||||
settings.advanced.jvm_args=Java虚拟机参数(不必填写)
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,MB)
|
||||
settings.advanced.jvm_args=Java 虚拟机参数(不必填写)
|
||||
settings.advanced.launcher_visibility.close=游戏启动后结束启动器
|
||||
settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器
|
||||
settings.advanced.launcher_visibility.hide_reopen=隐藏启动器并在游戏结束后重新打开
|
||||
settings.advanced.launcher_visibility.keep=保持启动器可见
|
||||
settings.advanced.launcher_visible=启动器可见性
|
||||
settings.advanced.minecraft_arguments=Minecraft额外参数(不必填写)
|
||||
settings.advanced.minecraft_arguments=Minecraft 额外参数(不必填写)
|
||||
settings.advanced.no_common=不使用公共路径
|
||||
settings.advanced.no_jvm_args=不添加默认的JVM参数(使用Java9时必勾)
|
||||
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
|
||||
settings.advanced.server_ip=直入服务器ip地址(不必填写,启动游戏后直接进入对应服务器)
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填写,如optirun)
|
||||
settings.advanced.no_jvm_args=不添加默认的 JVM 参数(使用 Java 9 时必勾)
|
||||
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
|
||||
settings.advanced.server_ip=直入服务器 IP 地址(不必填写,启动游戏后直接进入对应服务器)
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填写,如 optirun)
|
||||
|
||||
settings.cannot_remove_default_config=不能删除默认配置
|
||||
settings.custom=自定义
|
||||
settings.default=默认
|
||||
settings.failed_load=设置文件加载失败,可能是升级了启动器或被人工修改造成错误,是否清除?
|
||||
|
||||
settings.game=游戏设置
|
||||
settings.game.dimension=游戏窗口分辨率
|
||||
@@ -353,8 +273,8 @@ settings.game.exploration=浏览
|
||||
settings.game.fullscreen=全屏
|
||||
settings.game.game_directory=游戏路径(版本隔离)
|
||||
settings.game.game_directory.choose=选择游戏路径
|
||||
settings.game.java_directory=Java路径
|
||||
settings.game.java_directory.choose=选择Java路径
|
||||
settings.game.java_directory=Java 路径
|
||||
settings.game.java_directory.choose=选择 Java 路径
|
||||
settings.game.management=管理
|
||||
|
||||
settings.icon=游戏图标
|
||||
@@ -376,18 +296,13 @@ settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=账户
|
||||
settings.launcher.theme=主题
|
||||
|
||||
settings.max_memory=最大内存/MB
|
||||
settings.max_memory=最大内存(MB)
|
||||
settings.physical_memory=物理内存大小
|
||||
settings.run_directory=运行路径(版本隔离)
|
||||
settings.show_log=查看日志
|
||||
settings.tabs.assets_downloads=资源下载
|
||||
settings.tabs.game_download=游戏下载
|
||||
settings.tabs.installers=自动安装
|
||||
settings.test_game=测试游戏
|
||||
settings.type.global=全局版本设置(使用该设置的版本共用一套设定)
|
||||
settings.type=版本设置类型
|
||||
settings.type.special=单独版本设置(不会影响到其他版本的设定)
|
||||
settings.update_version=更新版本文件
|
||||
|
||||
update=启动器更新
|
||||
update.failed=检查更新失败
|
||||
@@ -395,7 +310,6 @@ update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
|
||||
update.should_open_link=是否更新?
|
||||
update.tooltip=更新
|
||||
|
||||
version.cannot_read=读取游戏版本失败,无法进行自动安装
|
||||
@@ -407,9 +321,8 @@ version.launch=启动游戏
|
||||
version.launch_script=生成启动脚本
|
||||
version.launch_script.failed=生成启动脚本失败
|
||||
version.launch_script.save=保存启动脚本
|
||||
version.launch_script.success=启动脚本已生成完毕: %s.
|
||||
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
|
||||
version.manage.redownload_json=重新下载版本配置(minecraft.json)
|
||||
version.launch_script.success=启动脚本已生成完毕:%s
|
||||
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
|
||||
version.manage.remove=删除该版本
|
||||
version.manage.remove.confirm=真的要删除版本 %s 吗?
|
||||
version.manage.remove_libraries=删除所有库文件
|
||||
@@ -419,10 +332,6 @@ version.settings=游戏设置
|
||||
version.update=更新
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.close=关闭
|
||||
wizard.failed=失败
|
||||
wizard.finish=完成
|
||||
wizard.help=帮助
|
||||
wizard.next=下一步 >
|
||||
wizard.steps=步骤
|
||||
wizard.summary=概要
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 653 KiB |
@@ -47,10 +47,9 @@ public abstract class Account {
|
||||
/**
|
||||
* Login with stored credentials.
|
||||
*
|
||||
* @throws ServerDisconnectException if an network error has occurred, in which case password login won't be tried.
|
||||
* @throws AuthenticationException if an error has occurred. If it's not a {@link ServerDisconnectException}, password login will be tried.
|
||||
* @throws CredentialExpiredException when the stored credentials has expired, in which case a password login will be performed
|
||||
*/
|
||||
public abstract AuthInfo logIn() throws ServerDisconnectException, AuthenticationException;
|
||||
public abstract AuthInfo logIn() throws CredentialExpiredException, AuthenticationException;
|
||||
|
||||
/**
|
||||
* Login with specified password.
|
||||
|
||||
+20
-3
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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
|
||||
@@ -18,8 +18,25 @@
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
/**
|
||||
* Thrown when the stored credentials has expired.
|
||||
* This exception indicates that a password login should be performed.
|
||||
*
|
||||
* @author huangyuhui
|
||||
* @author yushijinhun
|
||||
* @see Account#logIn()
|
||||
*/
|
||||
public class InvalidTokenException extends AuthenticationException {
|
||||
public class CredentialExpiredException extends AuthenticationException {
|
||||
|
||||
public CredentialExpiredException() {}
|
||||
|
||||
public CredentialExpiredException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CredentialExpiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CredentialExpiredException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth;
|
||||
|
||||
/**
|
||||
* throws if wrong password or logging fails for too many times.
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class InvalidCredentialsException extends AuthenticationException {
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth;
|
||||
|
||||
public class ServerResponseMalformedException extends AuthenticationException {
|
||||
|
||||
+46
-25
@@ -31,20 +31,24 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
private final String serverBaseURL;
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||
private AuthlibInjectorServer server;
|
||||
private ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader;
|
||||
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, UUID characterUUID, YggdrasilSession session) {
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, AuthlibInjectorServer server, ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader, String username, UUID characterUUID, YggdrasilSession session) {
|
||||
super(service, username, characterUUID, session);
|
||||
|
||||
this.injectorJarPath = injectorJarPath;
|
||||
this.serverBaseURL = serverBaseURL;
|
||||
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,36 +61,53 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
return inject(() -> super.logInWithPassword(password, selector));
|
||||
}
|
||||
|
||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> supplier) throws AuthenticationException {
|
||||
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
|
||||
GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL));
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
|
||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {
|
||||
// Pre-fetch metadata
|
||||
GetTask metadataFetchTask = new GetTask(NetworkUtils.toURL(server.getUrl()));
|
||||
Thread metadataFetchThread = Lang.thread(() -> {
|
||||
try {
|
||||
metadataFetchTask.run();
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Failed to pre-fetch Yggdrasil metadata", e);
|
||||
}
|
||||
}, "Yggdrasil metadata fetch thread");
|
||||
|
||||
AuthInfo info = supplier.get();
|
||||
// Update authlib-injector
|
||||
AuthlibInjectorArtifactInfo artifact;
|
||||
try {
|
||||
thread.join();
|
||||
|
||||
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL);
|
||||
|
||||
if (flag.get())
|
||||
arguments = arguments.addJVMArguments("-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), UTF_8));
|
||||
|
||||
return info.withArguments(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("Unable to get authlib injector jar path", e);
|
||||
artifact = authlibInjectorDownloader.get();
|
||||
} catch (IOException e) {
|
||||
throw new AuthenticationException("Failed to download authlib-injector", e);
|
||||
}
|
||||
|
||||
// Perform authentication
|
||||
AuthInfo info = loginAction.get();
|
||||
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl());
|
||||
|
||||
// Wait for metadata to be fetched
|
||||
try {
|
||||
metadataFetchThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Optional<String> metadata = Optional.ofNullable(metadataFetchTask.getResult());
|
||||
if (metadata.isPresent()) {
|
||||
arguments = arguments.addJVMArguments(
|
||||
"-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(metadata.get().getBytes(UTF_8)));
|
||||
}
|
||||
|
||||
return info.withArguments(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> toStorage() {
|
||||
Map<Object, Object> map = super.toStorage();
|
||||
map.put("serverBaseURL", serverBaseURL);
|
||||
map.put("serverBaseURL", server.getUrl());
|
||||
return map;
|
||||
}
|
||||
|
||||
public String getServerBaseURL() {
|
||||
return serverBaseURL;
|
||||
public AuthlibInjectorServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
}
|
||||
+35
-11
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth.authlibinjector;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
@@ -6,33 +23,38 @@ import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
|
||||
private ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader;
|
||||
private Function<String, AuthlibInjectorServer> serverLookup;
|
||||
|
||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier) {
|
||||
this.injectorJarPathSupplier = injectorJarPathSupplier;
|
||||
/**
|
||||
* @param serverLookup a function that looks up {@link AuthlibInjectorServer} by url
|
||||
*/
|
||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader, Function<String, AuthlibInjectorServer> serverLookup) {
|
||||
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||
this.serverLookup = serverLookup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object serverBaseURL, Proxy proxy) throws AuthenticationException {
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
if (!(serverBaseURL instanceof String) || !NetworkUtils.isURL((String) serverBaseURL))
|
||||
throw new IllegalArgumentException("Additional data should be server base url string for authlib injector accounts.");
|
||||
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
|
||||
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider((String) serverBaseURL), proxy),
|
||||
(String) serverBaseURL, injectorJarPathSupplier, username, null, null);
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
server, authlibInjectorDownloader, username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
@@ -49,7 +71,9 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
String apiRoot = tryCast(storage.get("serverBaseURL"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have API root."));
|
||||
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(apiRoot), proxy),
|
||||
apiRoot, injectorJarPathSupplier, username, session.getSelectedProfile().getId(), session);
|
||||
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
|
||||
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
server, authlibInjectorDownloader, username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
}
|
||||
+20
-21
@@ -17,35 +17,34 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
|
||||
public class AuthlibInjectorServerResponse {
|
||||
import java.nio.file.Path;
|
||||
|
||||
private final Meta meta;
|
||||
public class AuthlibInjectorArtifactInfo {
|
||||
|
||||
public AuthlibInjectorServerResponse() {
|
||||
this(new Meta());
|
||||
private int buildNumber;
|
||||
private String version;
|
||||
private Path location;
|
||||
|
||||
public AuthlibInjectorArtifactInfo(int buildNumber, String version, Path location) {
|
||||
this.buildNumber = buildNumber;
|
||||
this.version = version;
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
public AuthlibInjectorServerResponse(Meta meta) {
|
||||
this.meta = meta;
|
||||
public int getBuildNumber() {
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
public Meta getMeta() {
|
||||
return meta;
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public static class Meta {
|
||||
private final String serverName;
|
||||
public Path getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
public Meta() {
|
||||
this("");
|
||||
}
|
||||
|
||||
public Meta(String serverName) {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "authlib-injector [buildNumber=" + buildNumber + ", version=" + version + "]";
|
||||
}
|
||||
}
|
||||
-59
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth.authlibinjector;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Immutable
|
||||
public final class AuthlibInjectorBuildInfo {
|
||||
|
||||
private final int buildNumber;
|
||||
private final String url;
|
||||
|
||||
public AuthlibInjectorBuildInfo() {
|
||||
this(0, "");
|
||||
}
|
||||
|
||||
public AuthlibInjectorBuildInfo(int buildNumber, String url) {
|
||||
this.buildNumber = buildNumber;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getBuildNumber() {
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public static AuthlibInjectorBuildInfo requestBuildInfo() throws IOException, JsonParseException {
|
||||
return requestBuildInfo(UPDATE_URL);
|
||||
}
|
||||
|
||||
public static AuthlibInjectorBuildInfo requestBuildInfo(String updateUrl) throws IOException, JsonParseException {
|
||||
return JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(updateUrl)), AuthlibInjectorBuildInfo.class);
|
||||
}
|
||||
|
||||
public static final String UPDATE_URL = "https://authlib-injector.to2mbn.org/api/buildInfo";
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth.authlibinjector;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class AuthlibInjectorDownloader {
|
||||
|
||||
private static final String LATEST_BUILD_URL = "https://authlib-injector.yushi.moe/artifact/latest.json";
|
||||
|
||||
private Path artifactLocation;
|
||||
private Supplier<DownloadProvider> downloadProvider;
|
||||
|
||||
/**
|
||||
* @param artifactsDirectory where to save authlib-injector artifacts
|
||||
*/
|
||||
public AuthlibInjectorDownloader(Path artifactsDirectory, Supplier<DownloadProvider> downloadProvider) {
|
||||
this.artifactLocation = artifactsDirectory.resolve("authlib-injector.jar");
|
||||
this.downloadProvider = downloadProvider;
|
||||
}
|
||||
|
||||
public AuthlibInjectorArtifactInfo getArtifactInfo() throws IOException {
|
||||
synchronized (artifactLocation) {
|
||||
Optional<AuthlibInjectorArtifactInfo> local = getLocalArtifact();
|
||||
|
||||
try {
|
||||
update(local);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to download authlib-injector", e);
|
||||
if (!local.isPresent()) {
|
||||
throw e;
|
||||
}
|
||||
LOG.warning("Fallback to use cached artifact: " + local.get());
|
||||
}
|
||||
|
||||
return getLocalArtifact().orElseThrow(() -> new IOException("The updated authlib-inejector cannot be recognized"));
|
||||
}
|
||||
}
|
||||
|
||||
private void update(Optional<AuthlibInjectorArtifactInfo> local) throws IOException {
|
||||
AuthlibInjectorVersionInfo latest = getLatestArtifactInfo();
|
||||
|
||||
if (local.isPresent() && local.get().getBuildNumber() >= latest.buildNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(), Proxy.NO_PROXY,
|
||||
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||
.orElse(null))
|
||||
.run();
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Failed to download authlib-injector", e);
|
||||
}
|
||||
|
||||
LOG.info("Updated authlib-injector to " + latest.version);
|
||||
}
|
||||
|
||||
private AuthlibInjectorVersionInfo getLatestArtifactInfo() throws IOException {
|
||||
try {
|
||||
return JsonUtils.fromNonNullJson(
|
||||
NetworkUtils.doGet(
|
||||
new URL(downloadProvider.get().injectURL(LATEST_BUILD_URL))),
|
||||
AuthlibInjectorVersionInfo.class);
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed response", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<AuthlibInjectorArtifactInfo> getLocalArtifact() {
|
||||
if (!Files.isRegularFile(artifactLocation)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
try {
|
||||
return Optional.of(readArtifactInfo(artifactLocation));
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Bad authlib-injector artifact", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthlibInjectorArtifactInfo readArtifactInfo(Path location) throws IOException {
|
||||
try (JarFile jarFile = new JarFile(location.toFile())) {
|
||||
Attributes attributes = jarFile.getManifest().getMainAttributes();
|
||||
|
||||
String title = Optional.ofNullable(attributes.getValue("Implementation-Title"))
|
||||
.orElseThrow(() -> new IOException("Missing Implementation-Title"));
|
||||
if (!"authlib-injector".equals(title)) {
|
||||
throw new IOException("Bad Implementation-Title");
|
||||
}
|
||||
|
||||
String version = Optional.ofNullable(attributes.getValue("Implementation-Version"))
|
||||
.orElseThrow(() -> new IOException("Missing Implementation-Version"));
|
||||
|
||||
int buildNumber;
|
||||
try {
|
||||
buildNumber = Optional.ofNullable(attributes.getValue("Build-Number"))
|
||||
.map(Integer::parseInt)
|
||||
.orElseThrow(() -> new IOException("Missing Build-Number"));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException("Bad Build-Number", e);
|
||||
}
|
||||
return new AuthlibInjectorArtifactInfo(buildNumber, version, location.toAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
private class AuthlibInjectorVersionInfo {
|
||||
@SerializedName("build_number")
|
||||
public int buildNumber;
|
||||
|
||||
@SerializedName("version")
|
||||
public String version;
|
||||
|
||||
@SerializedName("download_url")
|
||||
public String downloadUrl;
|
||||
|
||||
@SerializedName("checksums")
|
||||
public Map<String, String> checksums;
|
||||
}
|
||||
|
||||
}
|
||||
+17
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth.authlibinjector;
|
||||
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;
|
||||
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth.authlibinjector;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jackhuang.hmcl.util.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
public class AuthlibInjectorServer {
|
||||
|
||||
public static AuthlibInjectorServer fetchServerInfo(String url) throws IOException {
|
||||
try {
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
|
||||
String name = extractServerName(response).orElse(url);
|
||||
return new AuthlibInjectorServer(url, name);
|
||||
} catch (JsonSyntaxException e) {
|
||||
throw new IOException("Malformed response", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<String> extractServerName(JsonObject response){
|
||||
return tryCast(response.get("meta"), JsonObject.class)
|
||||
.flatMap(meta -> tryCast(meta.get("serverName"), JsonPrimitive.class).map(JsonPrimitive::getAsString));
|
||||
}
|
||||
|
||||
private String url;
|
||||
private String name;
|
||||
|
||||
public AuthlibInjectorServer(String url, String name) {
|
||||
this.url = url;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return url + " (" + name + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return url.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this)
|
||||
return true;
|
||||
if (!(obj instanceof AuthlibInjectorServer))
|
||||
return false;
|
||||
AuthlibInjectorServer another = (AuthlibInjectorServer) obj;
|
||||
return this.url.equals(another.url);
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 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.auth.authlibinjector;
|
||||
|
||||
public class AuthlibInjectorServerInfo {
|
||||
private final String serverIp;
|
||||
private final String serverName;
|
||||
|
||||
public AuthlibInjectorServerInfo(String serverIp, String serverName) {
|
||||
this.serverIp = serverIp;
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public String getServerIp() {
|
||||
return serverIp;
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
}
|
||||
@@ -66,40 +66,54 @@ public class YggdrasilAccount extends Account {
|
||||
@Override
|
||||
public AuthInfo logIn() throws AuthenticationException {
|
||||
if (!canPlayOnline()) {
|
||||
logInWithToken();
|
||||
selectProfile(new SpecificCharacterSelector(characterUUID));
|
||||
if (service.validate(session.getAccessToken(), session.getClientToken())) {
|
||||
isOnline = true;
|
||||
} else {
|
||||
try {
|
||||
updateSession(service.refresh(session.getAccessToken(), session.getClientToken(), null), new SpecificCharacterSelector(characterUUID));
|
||||
} catch (RemoteAuthenticationException e) {
|
||||
if ("ForbiddenOperationException".equals(e.getRemoteName())) {
|
||||
throw new CredentialExpiredException(e);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AuthInfo logInWithPassword(String password) throws AuthenticationException {
|
||||
public AuthInfo logInWithPassword(String password) throws AuthenticationException {
|
||||
return logInWithPassword(password, new SpecificCharacterSelector(characterUUID));
|
||||
}
|
||||
|
||||
protected AuthInfo logInWithPassword(String password, CharacterSelector selector) throws AuthenticationException {
|
||||
session = service.authenticate(username, password, UUIDTypeAdapter.fromUUID(UUID.randomUUID()));
|
||||
selectProfile(selector);
|
||||
updateSession(service.authenticate(username, password, UUIDTypeAdapter.fromUUID(UUID.randomUUID())), selector);
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
private void selectProfile(CharacterSelector selector) throws AuthenticationException {
|
||||
if (session.getSelectedProfile() == null) {
|
||||
if (session.getAvailableProfiles() == null || session.getAvailableProfiles().length <= 0)
|
||||
/**
|
||||
* Updates the current session. This method shall be invoked after authenticate/refresh operation.
|
||||
* {@link #session} field shall be set only using this method. This method ensures {@link #session}
|
||||
* has a profile selected.
|
||||
*
|
||||
* @param acquiredSession the session acquired by making an authenticate/refresh request
|
||||
*/
|
||||
private void updateSession(YggdrasilSession acquiredSession, CharacterSelector selector) throws AuthenticationException {
|
||||
if (acquiredSession.getSelectedProfile() == null) {
|
||||
if (acquiredSession.getAvailableProfiles() == null || acquiredSession.getAvailableProfiles().length == 0)
|
||||
throw new NoCharacterException(this);
|
||||
|
||||
session = service.refresh(session.getAccessToken(), session.getClientToken(), selector.select(this, Arrays.asList(session.getAvailableProfiles())));
|
||||
this.session = service.refresh(
|
||||
acquiredSession.getAccessToken(),
|
||||
acquiredSession.getClientToken(),
|
||||
selector.select(this, Arrays.asList(acquiredSession.getAvailableProfiles())));
|
||||
} else {
|
||||
this.session = acquiredSession;
|
||||
}
|
||||
|
||||
characterUUID = session.getSelectedProfile().getId();
|
||||
}
|
||||
|
||||
private void logInWithToken() throws AuthenticationException {
|
||||
if (service.validate(session.getAccessToken(), session.getClientToken())) {
|
||||
isOnline = true;
|
||||
return;
|
||||
}
|
||||
session = service.refresh(session.getAccessToken(), session.getClientToken(), null);
|
||||
this.characterUUID = this.session.getSelectedProfile().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -83,8 +83,11 @@ public class YggdrasilService {
|
||||
try {
|
||||
requireEmpty(request(provider.getValidationURL(), createRequestWithCredentials(accessToken, clientToken)));
|
||||
return true;
|
||||
} catch (InvalidCredentialsException | InvalidTokenException e) {
|
||||
return false;
|
||||
} catch (RemoteAuthenticationException e) {
|
||||
if ("ForbiddenOperationException".equals(e.getRemoteName())) {
|
||||
return false;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,17 +148,6 @@ public class YggdrasilService {
|
||||
|
||||
private static void handleErrorMessage(ErrorResponse response) throws AuthenticationException {
|
||||
if (!StringUtils.isBlank(response.error)) {
|
||||
if (response.errorMessage != null && "ForbiddenOperationException".equals(response.error)) {
|
||||
if (response.errorMessage.contains("Invalid credentials"))
|
||||
throw new InvalidCredentialsException();
|
||||
|
||||
else if (response.errorMessage.contains("Invalid token"))
|
||||
throw new InvalidTokenException();
|
||||
|
||||
else if (response.errorMessage.contains("Invalid username or password"))
|
||||
throw new InvalidPasswordException();
|
||||
}
|
||||
|
||||
throw new RemoteAuthenticationException(response.error, response.errorMessage, response.cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.game.AssetObject;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -106,7 +107,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
flag = !file.exists();
|
||||
}
|
||||
if (flag) {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), assetObject.getHash());
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
dependencies.add(task);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download.game;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
@@ -56,7 +57,7 @@ public final class GameDownloadTask extends Task {
|
||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||
jar,
|
||||
dependencyManager.getProxy(),
|
||||
version.getDownloadInfo().getSha1()
|
||||
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
@@ -48,7 +49,7 @@ public final class LibraryDownloadTask extends Task {
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
|
||||
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
||||
file, dependencyManager.getProxy(), library.getDownload().getSha1());
|
||||
file, dependencyManager.getProxy(), new IntegrityCheck("SHA-1", library.getDownload().getSha1()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -84,6 +84,7 @@ public class DefaultLauncher extends Launcher {
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
|
||||
res.add("-Duser.home=" + options.getGameDir().getParent());
|
||||
|
||||
// Force using G1GC with its settings
|
||||
if (options.getJava().getParsedVersion() >= JavaVersion.JAVA_7) {
|
||||
res.add("-XX:+UnlockExperimentalVMOptions");
|
||||
res.add("-XX:+UseG1GC");
|
||||
@@ -103,6 +104,12 @@ public class DefaultLauncher extends Launcher {
|
||||
res.add("-XX:-OmitStackTraceInFastThrow");
|
||||
res.add("-Xmn128m");
|
||||
|
||||
// As 32-bit JVM allocate 320KB for stack by default rather than 64-bit version allocating 1MB,
|
||||
// causing Minecraft 1.13 crashed accounting for java.lang.StackOverflowError.
|
||||
if (options.getJava().getPlatform() == Platform.BIT_32) {
|
||||
res.add("-Xss1M");
|
||||
}
|
||||
|
||||
if (options.getMaxMemory() != null && options.getMaxMemory() > 0)
|
||||
res.add("-Xmx" + options.getMaxMemory() + "m");
|
||||
|
||||
|
||||
+17
-6
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
* 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
|
||||
@@ -15,10 +15,21 @@
|
||||
* 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.auth;
|
||||
package org.jackhuang.hmcl.mod;
|
||||
|
||||
/**
|
||||
* throws if wrong password.
|
||||
*/
|
||||
public class InvalidPasswordException extends AuthenticationException {
|
||||
public class CurseCompletionException extends Exception {
|
||||
public CurseCompletionException() {
|
||||
}
|
||||
|
||||
public CurseCompletionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CurseCompletionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CurseCompletionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,11 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -49,7 +51,7 @@ public final class CurseCompletionTask extends Task {
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager.
|
||||
* @param version the existent and physical version.
|
||||
* @param version the existent and physical version.
|
||||
*/
|
||||
public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version) {
|
||||
this(dependencyManager, version, null);
|
||||
@@ -59,8 +61,8 @@ public final class CurseCompletionTask extends Task {
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager.
|
||||
* @param version the existent and physical version.
|
||||
* @param manifest the CurseForgeModpack manifest.
|
||||
* @param version the existent and physical version.
|
||||
* @param manifest the CurseForgeModpack manifest.
|
||||
*/
|
||||
public CurseCompletionTask(DefaultDependencyManager dependencyManager, String version, CurseManifest manifest) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
@@ -96,6 +98,7 @@ public final class CurseCompletionTask extends Task {
|
||||
File root = repository.getVersionRoot(version);
|
||||
File run = repository.getRunDirectory(version);
|
||||
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
AtomicInteger finished = new AtomicInteger(0);
|
||||
|
||||
// Because in China, Curse is too difficult to visit,
|
||||
@@ -104,14 +107,32 @@ public final class CurseCompletionTask extends Task {
|
||||
manifest.getFiles().parallelStream()
|
||||
.map(file -> {
|
||||
updateProgress(finished.incrementAndGet(), manifest.getFiles().size());
|
||||
return Lang.ignoringException(() -> file.setFileName(NetworkUtils.detectFileName(file.getUrl(), dependencyManager.getProxy())), file);
|
||||
if (StringUtils.isBlank(file.getFileName())) {
|
||||
try {
|
||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl(), dependencyManager.getProxy()));
|
||||
} catch (IOException ioe) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), ioe);
|
||||
flag.set(false);
|
||||
return file;
|
||||
}
|
||||
} else
|
||||
return file;
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
FileUtils.writeText(new File(root, "manifest.json"), Constants.GSON.toJson(newManifest));
|
||||
|
||||
for (CurseManifestFile file : newManifest.getFiles())
|
||||
if (StringUtils.isNotBlank(file.getFileName()))
|
||||
dependencies.add(new FileDownloadTask(file.getUrl(), new File(run, "mods/" + file.getFileName()), dependencyManager.getProxy()));
|
||||
if (StringUtils.isNotBlank(file.getFileName())) {
|
||||
File dest = new File(run, "mods/" + file.getFileName());
|
||||
if (!dest.exists())
|
||||
dependencies.add(new FileDownloadTask(file.getUrl(), dest, dependencyManager.getProxy()));
|
||||
}
|
||||
|
||||
// Let this task fail if the curse manifest has not been completed.
|
||||
if (!flag.get())
|
||||
dependencies.add(Task.of(() -> {
|
||||
throw new CurseCompletionException();
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -113,7 +113,10 @@ public final class CurseInstallTask extends Task {
|
||||
throw new IOException("Unable to delete mod file " + oldFile);
|
||||
}
|
||||
|
||||
dependencies.add(new CurseCompletionTask(dependencyManager, name));
|
||||
File root = repository.getVersionRoot(name);
|
||||
FileUtils.writeText(new File(root, "manifest.json"), Constants.GSON.toJson(manifest));
|
||||
|
||||
dependencies.add(new CurseCompletionTask(dependencyManager, name, manifest));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, manifest.getOverrides(), manifest, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class CurseManifestFile implements Validation {
|
||||
return NetworkUtils.toURL("https://minecraft.curseforge.com/projects/" + projectID + "/files/" + fileID + "/download");
|
||||
}
|
||||
|
||||
public CurseManifestFile setFileName(String fileName) {
|
||||
public CurseManifestFile withFileName(String fileName) {
|
||||
return new CurseManifestFile(projectID, fileID, fileName, required);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.event.FailedEvent;
|
||||
import org.jackhuang.hmcl.util.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
@@ -35,6 +36,7 @@ import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
||||
|
||||
/**
|
||||
@@ -44,9 +46,38 @@ import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
||||
*/
|
||||
public class FileDownloadTask extends Task {
|
||||
|
||||
public static class IntegrityCheck {
|
||||
private String algorithm;
|
||||
private String checksum;
|
||||
|
||||
public IntegrityCheck(String algorithm, String checksum) {
|
||||
this.algorithm = requireNonNull(algorithm);
|
||||
this.checksum = requireNonNull(checksum);
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
public String getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
public MessageDigest createDigest() {
|
||||
return getDigest(algorithm);
|
||||
}
|
||||
|
||||
public void performCheck(MessageDigest digest) throws ChecksumMismatchException {
|
||||
String actualChecksum = String.format("%1$040x", new BigInteger(1, digest.digest()));
|
||||
if (!checksum.equalsIgnoreCase(actualChecksum)) {
|
||||
throw new ChecksumMismatchException(algorithm, checksum, actualChecksum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final URL url;
|
||||
private final File file;
|
||||
private final String hash;
|
||||
private final IntegrityCheck integrityCheck;
|
||||
private final int retry;
|
||||
private final Proxy proxy;
|
||||
private final EventManager<FailedEvent<URL>> onFailed = new EventManager<>();
|
||||
@@ -74,23 +105,23 @@ public class FileDownloadTask extends Task {
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param proxy the proxy.
|
||||
* @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, Proxy proxy, String hash) {
|
||||
this(url, file, proxy, hash, 5);
|
||||
public FileDownloadTask(URL url, File file, Proxy proxy, IntegrityCheck integrityCheck) {
|
||||
this(url, file, proxy, integrityCheck, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
* @param proxy the proxy.
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, Proxy proxy, String hash, int retry) {
|
||||
public FileDownloadTask(URL url, File file, Proxy proxy, IntegrityCheck integrityCheck, int retry) {
|
||||
this.url = url;
|
||||
this.file = file;
|
||||
this.hash = hash;
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.retry = retry;
|
||||
this.proxy = proxy;
|
||||
|
||||
@@ -159,7 +190,7 @@ public class FileDownloadTask extends Task {
|
||||
temp = FileUtils.createTempFile();
|
||||
rFile = new RandomAccessFile(temp, "rw");
|
||||
|
||||
MessageDigest digest = getDigest("SHA-1");
|
||||
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
||||
|
||||
stream = con.getInputStream();
|
||||
int lastDownloaded = 0, downloaded = 0;
|
||||
@@ -175,8 +206,9 @@ public class FileDownloadTask extends Task {
|
||||
if (read == -1)
|
||||
break;
|
||||
|
||||
if (hash != null)
|
||||
if (digest != null) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
|
||||
// Write buffer to file.
|
||||
rFile.write(buffer, 0, read);
|
||||
@@ -214,11 +246,9 @@ public class FileDownloadTask extends Task {
|
||||
if (downloaded != contentLength)
|
||||
throw new IllegalStateException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||
|
||||
// Check hash code
|
||||
if (hash != null) {
|
||||
String hashCode = String.format("%1$040x", new BigInteger(1, digest.digest()));
|
||||
if (!hash.equalsIgnoreCase(hashCode))
|
||||
throw new IllegalStateException("Unexpected hash code: " + hashCode + ", expected: " + hash);
|
||||
// Integrity check
|
||||
if (integrityCheck != null) {
|
||||
integrityCheck.performCheck(digest);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
@@ -29,6 +29,7 @@ import java.util.Collections;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Disposable task.
|
||||
@@ -185,6 +186,9 @@ public abstract class Task {
|
||||
}
|
||||
|
||||
public final void run() throws Exception {
|
||||
if (getSignificance().shouldLog())
|
||||
Logging.LOG.log(Level.FINE, "Executing task: " + getName());
|
||||
|
||||
for (Task task : getDependents())
|
||||
doSubTask(task);
|
||||
execute();
|
||||
|
||||
+18
-16
@@ -17,28 +17,30 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
public class I18nException extends Exception {
|
||||
private final String localizedMessage;
|
||||
import java.io.IOException;
|
||||
|
||||
public I18nException(String localizedMessage) {
|
||||
this.localizedMessage = localizedMessage;
|
||||
public class ChecksumMismatchException extends IOException {
|
||||
|
||||
private String algorithm;
|
||||
private String expectedChecksum;
|
||||
private String actualChecksum;
|
||||
|
||||
public ChecksumMismatchException(String algorithm, String expectedChecksum, String actualChecksum) {
|
||||
super("Incorrect checksum (" + algorithm + "), expected: " + expectedChecksum + ", actual: " + actualChecksum);
|
||||
this.algorithm = algorithm;
|
||||
this.expectedChecksum = expectedChecksum;
|
||||
this.actualChecksum = actualChecksum;
|
||||
}
|
||||
|
||||
public I18nException(String localizedMessage, Throwable suppressed) {
|
||||
addSuppressed(suppressed);
|
||||
|
||||
this.localizedMessage = localizedMessage;
|
||||
public String getAlgorithm() {
|
||||
return algorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalizedMessage() {
|
||||
return localizedMessage;
|
||||
public String getExpectedChecksum() {
|
||||
return expectedChecksum;
|
||||
}
|
||||
|
||||
public static String getStackTrace(Throwable e) {
|
||||
if (e instanceof I18nException)
|
||||
return e.getLocalizedMessage();
|
||||
else
|
||||
return StringUtils.getStackTrace(e);
|
||||
public String getActualChecksum() {
|
||||
return actualChecksum;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,11 @@ package org.jackhuang.hmcl.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import org.jackhuang.hmcl.game.Argument;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.RuledArgument;
|
||||
@@ -65,6 +70,9 @@ public final class Constants {
|
||||
javafx.application.Platform.runLater(s);
|
||||
};
|
||||
|
||||
// lazy loading
|
||||
public static final ObjectBinding<Image> DEFAULT_ICON = Bindings.createObjectBinding(() -> new Image("/assets/img/icon.png"));
|
||||
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.enableComplexMapKeySerialization()
|
||||
.setPrettyPrinting()
|
||||
|
||||
@@ -20,10 +20,8 @@ package org.jackhuang.hmcl.util;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.net.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
@@ -138,9 +136,9 @@ public final class NetworkUtils {
|
||||
String disposition = conn.getHeaderField("Content-Disposition");
|
||||
if (disposition == null || !disposition.contains("filename=")) {
|
||||
String u = conn.getURL().toString();
|
||||
return substringAfterLast(u, '/');
|
||||
return Lang.invoke(() -> URLDecoder.decode(substringAfterLast(u, '/'), StandardCharsets.UTF_8.name()));
|
||||
} else
|
||||
return removeSurrounding(substringAfter(disposition, "filename="), "\"");
|
||||
return Lang.invoke(() -> URLDecoder.decode(removeSurrounding(substringAfter(disposition, "filename="), "\""), StandardCharsets.UTF_8.name()));
|
||||
}
|
||||
|
||||
public static URL toURL(String str) {
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
# Hello Minecraft! Launcher [](https://travis-ci.org/huanghongxun/HMCL)
|
||||
# Hello Minecraft! Launcher [](https://ci.huangyuhui.net/job/HMCL)
|
||||
GPL v3, see http://www.gnu.org/licenses/gpl.html
|
||||
|
||||
## Introduction
|
||||
|
||||
HMCL is a Minecraft launcher which supports Mod management, game customizing, auto installing(Forge, LiteLoader and OptiFine), modpack creating, UI customizing and so on.
|
||||
|
||||
## Downloads
|
||||
|
||||
Binary version can be found here: https://ci.huangyuhui.net/job/HMCL
|
||||
|
||||
Archives in GitHub releases are built by closed CIs, uploaded here for backup.
|
||||
|
||||
## Contribution
|
||||
|
||||
If you want to submit a pull request, there're some requirements:
|
||||
* IDE: Intellij IDEA.
|
||||
* Compiler: Java 1.8.
|
||||
* Do NOT modify `gradle` files.
|
||||
|
||||
## HMCLCore
|
||||
|
||||
Now HMCLCore is independent and you can use HMCLCore as a library to launch your game.
|
||||
|
||||
### GameRepository
|
||||
|
||||
Create a game repository `repository` to manage a minecraft installation. Like this.
|
||||
```java
|
||||
DefaultGameRepository repository = new DefaultGameRepository(new File(".minecraft").getAbsoluteFile());
|
||||
@@ -35,17 +44,23 @@ DefaultLauncher launcher = new DefaultLauncher(
|
||||
.logIn(), // account
|
||||
// or new AccountBuilder.Builder()
|
||||
// .setUsername("someone@xxx.com")
|
||||
// .setPassword("123")
|
||||
// .create(YggdrasilAccountFactory.INSTANCE)
|
||||
// .setPassword("someone's password")
|
||||
// // for Mojang account
|
||||
// .create(new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE))
|
||||
// // for Authlib Injector account
|
||||
// .create(new AuthlibInjectorAccountFactory(
|
||||
// new AuthlibInjectorDownloader(new File("path to save executables of authlib injector"),
|
||||
// () -> MojangYggdrasilProvider.INSTANCE)::getArtifactInfo,
|
||||
// () -> AuthlibInjectorServer.fetchServerInfo("Your authlib injector auth server")))
|
||||
// .logIn()
|
||||
new LaunchOptions.Builder()
|
||||
.setGameDir(repository.getBaseDirectory())
|
||||
.setGameDir(repository.getBaseDirectory()) // directory that the game saves settings to
|
||||
.setMaxMemory(...)
|
||||
.setJava(...)
|
||||
.setJavaArgs(...)
|
||||
.setMinecraftArgs(...)
|
||||
.setHeight(...)
|
||||
.setWidth(...)
|
||||
.setJava(...) // executable of JVM
|
||||
.setJavaArgs(...) // additional Java VM arguments
|
||||
.setMinecraftArgs(...) // additional Minecraft arguments
|
||||
.setHeight(...) // height of game window
|
||||
.setWidth(...) // width of game window
|
||||
...
|
||||
.create(),
|
||||
new ProcessListener() { // listening the process state.
|
||||
@@ -68,23 +83,37 @@ If you want the command line, just call `launcher.getRawCommandLine`. Also, `Str
|
||||
### Downloading
|
||||
HMCLCore just owns a simple way to download a new game.
|
||||
```java
|
||||
DefaultDependencyManager dependency = new DefaultDependencyManager(repository, MojangDownloadProvider.INSTANCE, proxy);
|
||||
DefaultDependencyManager dependency = new DefaultDependencyManager(repository, new MojangDownloadProvider(), proxy);
|
||||
```
|
||||
`repository` is your `GameRepository`. `MojangDownloadProvider.INSTANCE` means that we download files from mojang servers. If you want BMCLAPI, `BMCLAPIDownloadProvider.INSTANCE` is just for you. `proxy` is `java.net.Proxy`, if you have a proxy, put it here, or `Proxy.NO_PROXY`.
|
||||
|
||||
Now `GameBuilder` can build a game.
|
||||
```
|
||||
```java
|
||||
Task gameBuildingTask = dependency.gameBuilder()
|
||||
.name("test")
|
||||
.gameVersion("1.12") // Minecraft version
|
||||
.version("forge", "14.21.1.2426") // Forge version
|
||||
.version("liteloader", "1.12-SNAPSHOT-4") // LiteLoader version
|
||||
.version("optifine", "HD_U_C4") // OptiFine version
|
||||
.buildAsync()
|
||||
.buildAsync();
|
||||
```
|
||||
|
||||
Nowadays HMCLCore only supports Forge, LiteLoader and OptiFine auto-installing.
|
||||
`buildAsync` will return a `Task`, you can call `Task.executor()::start` or simply `Task::start` to start this task. If you want to monitor the execution of tasks, you should see `TaskExecutor` and `Task::executor`.
|
||||
|
||||
### Modpack installing
|
||||
|
||||
HMCLCore supports Curse, MultiMC modpack.
|
||||
|
||||
```java
|
||||
// Installing curse modpack
|
||||
new CurseInstallTask(dependency, modpackZipFile, CurseManifest.readCurseForgeModpackManifest(modpackZipFile), "name of the new game");
|
||||
|
||||
// Installing MultiMC modpack
|
||||
new MultiMCModpackInstallTask(dependency, modpackZipFile, MultiMCInstanceConfiguration.readMultiMCModpackManifest(modpackZipFile), "name of the new game");
|
||||
// ** IMPORTANT **: You should read game settings from MultiMCInstanceConfiguration
|
||||
```
|
||||
|
||||
## HMCL
|
||||
JavaFX version of HMCL does not support old APIs.
|
||||
|
||||
No plugin API.
|
||||
Reference in new issue
Block a user