Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5f72af513 | ||
|
|
1532893c5f | ||
|
|
a3fbc9595a | ||
|
|
f789343b6e | ||
|
|
ff74417352 | ||
|
|
a0ff843d0c | ||
|
|
da93cb0dbb | ||
|
|
0de446c608 | ||
|
|
03bccf16d4 | ||
|
|
635ae8c11e | ||
|
|
dfd5a82112 | ||
|
|
7dad8d452f | ||
|
|
07f8294876 | ||
|
|
7185cc410b | ||
|
|
2d985af60d | ||
|
|
bee2e64482 | ||
|
|
d5dd43c0c1 | ||
|
|
09470eeb8a | ||
|
|
094249a6fa | ||
|
|
6b5302907a | ||
|
|
adc5b08765 | ||
|
|
0150e2a8d7 | ||
|
|
45eaa0c60f | ||
|
|
66ed683196 | ||
|
|
aa4239721e | ||
|
|
5b0b926cbd | ||
|
|
929ce398eb | ||
|
|
b3fdbe021d | ||
|
|
423bd68ce8 | ||
|
|
9bffc68c70 | ||
|
|
b7a5a60091 | ||
|
|
333b11c19f | ||
|
|
fbd4d1b772 | ||
|
|
06a58514b3 | ||
|
|
80815fc3a0 | ||
|
|
b97c277a3f | ||
|
|
310d693e2b | ||
|
|
c1596f6a11 | ||
|
|
76a04343d9 | ||
|
|
25261c99f1 | ||
|
|
d0b994fa9d | ||
|
|
f259601397 | ||
|
|
87904109ca | ||
|
|
785790ccc4 | ||
|
|
759921d00e | ||
|
|
e9db183d54 | ||
|
|
a853374a88 | ||
|
|
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 |
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,19 +137,6 @@ 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");
|
||||
@@ -155,12 +144,11 @@ public final class Launcher extends Application {
|
||||
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 = "https://hmcl.huangyuhui.net";
|
||||
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))));
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,10 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
import org.jackhuang.hmcl.util.CommandBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -42,10 +44,10 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendJvmArgs(List<String> result) {
|
||||
super.appendJvmArgs(result);
|
||||
|
||||
result.add("-Dminecraft.launcher.version=" + Launcher.VERSION);
|
||||
result.add("-Dminecraft.launcher.brand=" + Launcher.NAME);
|
||||
protected Map<String, String> getConfigurations() {
|
||||
Map<String, String> res = super.getConfigurations();
|
||||
res.put("${launcher_name}", Launcher.NAME);
|
||||
res.put("${launcher_version}", Launcher.VERSION);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,11 @@ 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;
|
||||
@@ -36,6 +37,7 @@ import org.jackhuang.hmcl.task.*;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
@@ -49,6 +51,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();
|
||||
@@ -104,16 +107,14 @@ public final class LauncherHelper {
|
||||
}
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(Launcher.i18n("account.methods"), variables -> {
|
||||
.then(Task.of(i18n("account.methods"), variables -> {
|
||||
try {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
} catch (ServerDisconnectException e) {
|
||||
variables.set("account",
|
||||
account.playOffline().orElseThrow(() -> e));
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
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)))
|
||||
@@ -131,12 +132,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 -> {
|
||||
@@ -148,12 +149,12 @@ public final class LauncherHelper {
|
||||
else
|
||||
launchingStepsPane.setCancel(it -> {
|
||||
process.stop();
|
||||
Controllers.closeDialog(it);
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
} else
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
|
||||
}))
|
||||
@@ -172,13 +173,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()) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
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);
|
||||
@@ -196,7 +215,7 @@ public final class LauncherHelper {
|
||||
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.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
@@ -207,10 +226,10 @@ public final class LauncherHelper {
|
||||
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(Launcher.i18n("launch.advice.java8_1_13"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
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(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
@@ -218,24 +237,24 @@ public final class LauncherHelper {
|
||||
// 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) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -249,8 +268,9 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE)
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
if (state == LoadingState.DONE) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
launchingStepsPane.setTitle(state.getLocalizedMessage());
|
||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
@@ -286,15 +306,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
|
||||
@@ -413,16 +425,16 @@ 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;
|
||||
}
|
||||
|
||||
logWindow.show();
|
||||
logWindow.onDone.register(() -> {
|
||||
logWindow.logLine("Commands: " + StringUtils.makeCommand(process.getCommands()), Log4jLevel.INFO);
|
||||
logWindow.logLine("Command: " + new CommandBuilder().addAll(process.getCommands()).toString(), Log4jLevel.INFO);
|
||||
for (Map.Entry<String, Log4jLevel> entry : logs)
|
||||
logWindow.logLine(entry.getKey(), entry.getValue());
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -22,19 +22,14 @@ 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.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.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -55,7 +50,9 @@ 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, Accounts::getOrCreateAuthlibInjectorServer))
|
||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.INSTANCE.getDownloadProvider())::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer))
|
||||
);
|
||||
|
||||
public static String getAccountType(Account account) {
|
||||
@@ -73,24 +70,8 @@ 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()
|
||||
return ConfigHolder.CONFIG.authlibInjectorServers.stream()
|
||||
.filter(server -> url.equals(server.getUrl()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
@@ -104,7 +85,7 @@ public final class Accounts {
|
||||
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
|
||||
}
|
||||
|
||||
Settings.SETTINGS.authlibInjectorServers.add(server);
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.add(server);
|
||||
return server;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,12 +17,21 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.hildan.fxgson.creators.ObservableListCreator;
|
||||
import org.hildan.fxgson.creators.ObservableMapCreator;
|
||||
import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.util.FileTypeAdapter;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
@@ -36,9 +45,25 @@ 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 {
|
||||
|
||||
private static final Gson CONFIG_GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
|
||||
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
|
||||
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
|
||||
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public static Config fromJson(String json) throws JsonParseException {
|
||||
return CONFIG_GSON.fromJson(json, Config.class);
|
||||
}
|
||||
|
||||
@SerializedName("last")
|
||||
public final StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
|
||||
@@ -79,7 +104,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,10 +124,17 @@ public final class Config implements Cloneable {
|
||||
@SerializedName("logLines")
|
||||
public final IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("firstLaunch")
|
||||
public final BooleanProperty firstLaunch = new SimpleBooleanProperty(true);
|
||||
|
||||
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
public String toJson() {
|
||||
return CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config clone() {
|
||||
return Settings.GSON.fromJson(Settings.GSON.toJson(this), Config.class);
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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.setting;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
public final class ConfigHolder {
|
||||
|
||||
private ConfigHolder() {}
|
||||
|
||||
public static final String CONFIG_FILENAME = "hmcl.json";
|
||||
public static final Path CONFIG_PATH = Paths.get(CONFIG_FILENAME).toAbsolutePath();
|
||||
public static final Config CONFIG = initSettings();
|
||||
|
||||
private static Config initSettings() {
|
||||
Config config = new Config();
|
||||
if (Files.exists(CONFIG_PATH)) {
|
||||
try {
|
||||
Config deserialized = Config.fromJson(new String(Files.readAllBytes(CONFIG_PATH), UTF_8));
|
||||
if (deserialized == null) {
|
||||
LOG.finer("Settings file is empty, use the default settings.");
|
||||
} else {
|
||||
config = deserialized;
|
||||
}
|
||||
LOG.finest("Initialized settings.");
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
static void saveConfig(Config config) {
|
||||
try {
|
||||
Files.write(CONFIG_PATH, config.toJson().getBytes(UTF_8));
|
||||
} catch (IOException ex) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,9 @@ package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
|
||||
import org.jackhuang.hmcl.game.HMCLDependencyManager;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
@@ -83,8 +86,18 @@ public final class Profile {
|
||||
nameProperty.set(name);
|
||||
}
|
||||
|
||||
public Profile() {
|
||||
this("Default");
|
||||
private BooleanProperty useRelativePathProperty = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||
|
||||
public BooleanProperty useRelativePathProperty() {
|
||||
return useRelativePathProperty();
|
||||
}
|
||||
|
||||
public boolean isUseRelativePath() {
|
||||
return useRelativePathProperty.get();
|
||||
}
|
||||
|
||||
public void setUseRelativePath(boolean useRelativePath) {
|
||||
useRelativePathProperty.set(useRelativePath);
|
||||
}
|
||||
|
||||
public Profile(String name) {
|
||||
@@ -116,6 +129,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;
|
||||
@@ -152,6 +166,7 @@ public final class Profile {
|
||||
return new ToStringBuilder(this)
|
||||
.append("gameDir", getGameDir())
|
||||
.append("name", getName())
|
||||
.append("useRelativePath", isUseRelativePath())
|
||||
.toString();
|
||||
}
|
||||
|
||||
@@ -159,6 +174,7 @@ public final class Profile {
|
||||
nameProperty.addListener(listener);
|
||||
globalProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
useRelativePathProperty.addListener(listener);
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
@@ -175,6 +191,7 @@ public final class Profile {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("global", context.serialize(src.getGlobal()));
|
||||
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
|
||||
jsonObject.addProperty("useRelativePath", src.isUseRelativePath());
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
@@ -187,6 +204,8 @@ public final class Profile {
|
||||
|
||||
Profile profile = new Profile("Default", new File(gameDir));
|
||||
profile.setGlobal(context.deserialize(obj.get("global"), VersionSetting.class));
|
||||
|
||||
profile.setUseRelativePath(Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
return profile;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Profiles {
|
||||
|
||||
@@ -30,9 +30,9 @@ public final class Profiles {
|
||||
public static String getProfileDisplayName(Profile profile) {
|
||||
switch (profile.getName()) {
|
||||
case Profiles.DEFAULT_PROFILE:
|
||||
return Launcher.i18n("profile.default");
|
||||
return i18n("profile.default");
|
||||
case Profiles.HOME_PROFILE:
|
||||
return Launcher.i18n("profile.home");
|
||||
return i18n("profile.home");
|
||||
default:
|
||||
return profile.getName();
|
||||
}
|
||||
|
||||
@@ -17,21 +17,11 @@
|
||||
*/
|
||||
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;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
import org.hildan.fxgson.creators.ObservableListCreator;
|
||||
import org.hildan.fxgson.creators.ObservableMapCreator;
|
||||
import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
@@ -41,9 +31,8 @@ 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;
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
@@ -59,30 +48,20 @@ 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()
|
||||
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
|
||||
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
|
||||
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
|
||||
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public static final String SETTINGS_FILE_NAME = "hmcl.json";
|
||||
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
|
||||
|
||||
public static 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 = ConfigHolder.CONFIG.firstLaunch.get();
|
||||
ConfigHolder.CONFIG.firstLaunch.set(false);
|
||||
|
||||
loadProxy();
|
||||
|
||||
for (Iterator<Map<Object, Object>> iterator = SETTINGS.accounts.iterator(); iterator.hasNext();) {
|
||||
for (Iterator<Map<Object, Object>> iterator = ConfigHolder.CONFIG.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) {
|
||||
@@ -103,7 +82,9 @@ public class Settings {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
SETTINGS.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
this.selectedAccount.set(accounts.get(ConfigHolder.CONFIG.selectedAccount.get()));
|
||||
|
||||
checkProfileMap();
|
||||
|
||||
@@ -118,46 +99,26 @@ public class Settings {
|
||||
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
|
||||
}
|
||||
|
||||
private static Config initSettings() {
|
||||
Config c = new Config();
|
||||
if (SETTINGS_FILE.exists())
|
||||
try {
|
||||
String str = FileUtils.readText(SETTINGS_FILE);
|
||||
if (StringUtils.isBlank(str))
|
||||
Logging.LOG.finer("Settings file is empty, use the default settings.");
|
||||
else {
|
||||
Config d = GSON.fromJson(str, Config.class);
|
||||
if (d != null)
|
||||
c = d;
|
||||
}
|
||||
Logging.LOG.finest("Initialized settings.");
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
SETTINGS.accounts.clear();
|
||||
for (Account account : accounts.values()) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", Accounts.getAccountType(account));
|
||||
SETTINGS.accounts.add(storage);
|
||||
}
|
||||
|
||||
FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to save config", ex);
|
||||
ConfigHolder.CONFIG.accounts.clear();
|
||||
for (Account account : accounts.values()) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", Accounts.getAccountType(account));
|
||||
ConfigHolder.CONFIG.accounts.add(storage);
|
||||
}
|
||||
ConfigHolder.saveConfig(ConfigHolder.CONFIG);
|
||||
}
|
||||
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.commonDirectory.get()) {
|
||||
public boolean isFirstLaunch() {
|
||||
return firstLaunch;
|
||||
}
|
||||
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", ConfigHolder.CONFIG.commonDirectory.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.commonDirectory.set(get());
|
||||
ConfigHolder.CONFIG.commonDirectory.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -174,7 +135,7 @@ public class Settings {
|
||||
this.commonPath.set(commonPath);
|
||||
}
|
||||
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.localization.get());
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(ConfigHolder.CONFIG.localization.get());
|
||||
|
||||
public Locales.SupportedLocale getLocale() {
|
||||
return locale;
|
||||
@@ -182,7 +143,7 @@ public class Settings {
|
||||
|
||||
public void setLocale(Locales.SupportedLocale locale) {
|
||||
this.locale = locale;
|
||||
SETTINGS.localization.set(Locales.getNameByLocale(locale));
|
||||
ConfigHolder.CONFIG.localization.set(Locales.getNameByLocale(locale));
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -192,7 +153,7 @@ public class Settings {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.proxyType.get());
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(ConfigHolder.CONFIG.proxyType.get());
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType;
|
||||
@@ -200,62 +161,62 @@ public class Settings {
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
SETTINGS.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
|
||||
ConfigHolder.CONFIG.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
|
||||
save();
|
||||
loadProxy();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return SETTINGS.proxyHost.get();
|
||||
return ConfigHolder.CONFIG.proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
SETTINGS.proxyHost.set(proxyHost);
|
||||
ConfigHolder.CONFIG.proxyHost.set(proxyHost);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return SETTINGS.proxyPort.get();
|
||||
return ConfigHolder.CONFIG.proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
SETTINGS.proxyPort.set(proxyPort);
|
||||
ConfigHolder.CONFIG.proxyPort.set(proxyPort);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return SETTINGS.proxyUser.get();
|
||||
return ConfigHolder.CONFIG.proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
SETTINGS.proxyUser.set(proxyUser);
|
||||
ConfigHolder.CONFIG.proxyUser.set(proxyUser);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return SETTINGS.proxyPass.get();
|
||||
return ConfigHolder.CONFIG.proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
SETTINGS.proxyPass.set(proxyPass);
|
||||
ConfigHolder.CONFIG.proxyPass.set(proxyPass);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxy() {
|
||||
return SETTINGS.hasProxy.get();
|
||||
return ConfigHolder.CONFIG.hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
SETTINGS.hasProxy.set(hasProxy);
|
||||
ConfigHolder.CONFIG.hasProxy.set(hasProxy);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() {
|
||||
return SETTINGS.hasProxyAuth.get();
|
||||
return ConfigHolder.CONFIG.hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
SETTINGS.hasProxyAuth.set(hasProxyAuth);
|
||||
ConfigHolder.CONFIG.hasProxyAuth.set(hasProxyAuth);
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -286,21 +247,21 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return Font.font(SETTINGS.fontFamily.get(), SETTINGS.fontSize.get());
|
||||
return Font.font(ConfigHolder.CONFIG.fontFamily.get(), ConfigHolder.CONFIG.fontSize.get());
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
SETTINGS.fontFamily.set(font.getFamily());
|
||||
SETTINGS.fontSize.set(font.getSize());
|
||||
ConfigHolder.CONFIG.fontFamily.set(font.getFamily());
|
||||
ConfigHolder.CONFIG.fontSize.set(font.getSize());
|
||||
save();
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return Math.max(SETTINGS.logLines.get(), 100);
|
||||
return Math.max(ConfigHolder.CONFIG.logLines.get(), 100);
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
SETTINGS.logLines.set(logLines);
|
||||
ConfigHolder.CONFIG.logLines.set(logLines);
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -316,7 +277,7 @@ public class Settings {
|
||||
private void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.values().stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.filter(it -> !SETTINGS.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
|
||||
.filter(it -> !ConfigHolder.CONFIG.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
|
||||
.collect(toList())
|
||||
.forEach(this::deleteAccount);
|
||||
}
|
||||
@@ -326,14 +287,14 @@ public class Settings {
|
||||
****************************************/
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
return DownloadProviders.getDownloadProvider(SETTINGS.downloadType.get());
|
||||
return DownloadProviders.getDownloadProvider(ConfigHolder.CONFIG.downloadType.get());
|
||||
}
|
||||
|
||||
public void setDownloadProvider(DownloadProvider downloadProvider) {
|
||||
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
|
||||
if (index == -1)
|
||||
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
|
||||
SETTINGS.downloadType.set(index);
|
||||
ConfigHolder.CONFIG.downloadType.set(index);
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -341,7 +302,7 @@ public class Settings {
|
||||
* ACCOUNTS *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.selectedAccount.get())) {
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", null) {
|
||||
@Override
|
||||
public Account get() {
|
||||
Account a = super.get();
|
||||
@@ -363,7 +324,7 @@ public class Settings {
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
ConfigHolder.CONFIG.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -413,12 +374,12 @@ public class Settings {
|
||||
* BACKGROUND *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.backgroundImage.get()) {
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", ConfigHolder.CONFIG.backgroundImage.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.backgroundImage.set(get());
|
||||
ConfigHolder.CONFIG.backgroundImage.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -435,12 +396,12 @@ public class Settings {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.backgroundImageType.get())) {
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(ConfigHolder.CONFIG.backgroundImageType.get())) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.backgroundImageType.set(get().ordinal());
|
||||
ConfigHolder.CONFIG.backgroundImageType.set(get().ordinal());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -461,12 +422,12 @@ public class Settings {
|
||||
* THEME *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.theme.get()).orElse(Theme.BLUE)) {
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(ConfigHolder.CONFIG.theme.get()).orElse(Theme.BLUE)) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.theme.set(get().getName().toLowerCase());
|
||||
ConfigHolder.CONFIG.theme.set(get().getName().toLowerCase());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -490,19 +451,19 @@ public class Settings {
|
||||
public Profile getSelectedProfile() {
|
||||
checkProfileMap();
|
||||
|
||||
if (!hasProfile(SETTINGS.selectedProfile.get())) {
|
||||
if (!hasProfile(ConfigHolder.CONFIG.selectedProfile.get())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
|
||||
SETTINGS.selectedProfile.set(selectedProfile);
|
||||
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile);
|
||||
save();
|
||||
});
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
return getProfile(SETTINGS.selectedProfile.get());
|
||||
return getProfile(ConfigHolder.CONFIG.selectedProfile.get());
|
||||
}
|
||||
|
||||
public void setSelectedProfile(Profile selectedProfile) {
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.selectedProfile.get())) {
|
||||
SETTINGS.selectedProfile.set(selectedProfile.getName());
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), ConfigHolder.CONFIG.selectedProfile.get())) {
|
||||
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile.getName());
|
||||
save();
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
@@ -520,7 +481,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
return SETTINGS.configurations;
|
||||
return ConfigHolder.CONFIG.configurations;
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
@@ -551,8 +512,12 @@ public class Settings {
|
||||
|
||||
private void checkProfileMap() {
|
||||
if (getProfileMap().isEmpty()) {
|
||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, new Profile(Profiles.DEFAULT_PROFILE));
|
||||
getProfileMap().put(Profiles.HOME_PROFILE, new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE);
|
||||
current.setUseRelativePath(true);
|
||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, current);
|
||||
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY);
|
||||
getProfileMap().put(Profiles.HOME_PROFILE, home);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,6 +544,6 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Config getRawConfig() {
|
||||
return SETTINGS.clone();
|
||||
return ConfigHolder.CONFIG.clone();
|
||||
}
|
||||
}
|
||||
@@ -31,13 +31,15 @@ import java.util.logging.Level;
|
||||
|
||||
public class Theme {
|
||||
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
||||
public static final Theme DARK_BLUE = new Theme("dark_blue", "#283593");
|
||||
public static final Theme GREEN = new Theme("green", "#43A047");
|
||||
public static final Theme ORANGE = new Theme("orange", "#E67E22");
|
||||
public static final Theme PURPLE = new Theme("purple", "#9C27B0");
|
||||
public static final Theme RED = new Theme("red", "#F44336");
|
||||
|
||||
public static final Theme[] VALUES = new Theme[]{BLUE, DARK_BLUE, GREEN, ORANGE, PURPLE, RED};
|
||||
public static final Color[] SUGGESTED_COLORS = new Color[]{
|
||||
Color.web("#5C6BC0"), // blue
|
||||
Color.web("#283593"), // dark blue
|
||||
Color.web("#43A047"), // green
|
||||
Color.web("#E67E22"), // orange
|
||||
Color.web("#9C27B0"), // purple
|
||||
Color.web("#F44336") // red
|
||||
};
|
||||
|
||||
private final String color;
|
||||
private final String name;
|
||||
@@ -68,8 +70,7 @@ public class Theme {
|
||||
}
|
||||
|
||||
public String[] getStylesheets() {
|
||||
String name = isCustom() ? BLUE.getName() : this.name;
|
||||
String css = Theme.class.getResource("/assets/css/" + name + ".css").toExternalForm();
|
||||
String css;
|
||||
try {
|
||||
File temp = File.createTempFile("hmcl", ".css");
|
||||
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
|
||||
@@ -77,7 +78,8 @@ public class Theme {
|
||||
.replace("%font-color%", getColorDisplayName(getForegroundColor())));
|
||||
css = temp.toURI().toString();
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet", e);
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
|
||||
css = Theme.class.getResource("/assets/css/blue.css").toExternalForm();
|
||||
}
|
||||
|
||||
return new String[]{
|
||||
@@ -98,10 +100,6 @@ public class Theme {
|
||||
if (name == null)
|
||||
return Optional.empty();
|
||||
|
||||
for (Theme theme : VALUES)
|
||||
if (theme.name.equalsIgnoreCase(name))
|
||||
return Optional.of(theme);
|
||||
|
||||
if (name.startsWith("#"))
|
||||
try {
|
||||
Color.web(name);
|
||||
|
||||
@@ -21,13 +21,13 @@ import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -35,7 +35,6 @@ public class AccountLoginPane extends StackPane {
|
||||
private final Account oldAccount;
|
||||
private final Consumer<AuthInfo> success;
|
||||
private final Runnable failed;
|
||||
private final Consumer<Region> closeConsumer;
|
||||
|
||||
@FXML
|
||||
private Label lblUsername;
|
||||
@@ -43,11 +42,10 @@ public class AccountLoginPane extends StackPane {
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
|
||||
public AccountLoginPane(Account oldAccount, Consumer<Region> closeConsumer, Consumer<AuthInfo> success, Runnable failed) {
|
||||
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
this.oldAccount = oldAccount;
|
||||
this.success = success;
|
||||
this.failed = failed;
|
||||
this.closeConsumer = closeConsumer;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
|
||||
|
||||
@@ -70,9 +68,9 @@ public class AccountLoginPane extends StackPane {
|
||||
Object account = variable.get("login");
|
||||
if (account instanceof AuthInfo) {
|
||||
success.accept(((AuthInfo) account));
|
||||
closeConsumer.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else if (account instanceof NoSelectedCharacterException) {
|
||||
closeConsumer.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
|
||||
}
|
||||
@@ -84,6 +82,6 @@ public class AccountLoginPane extends StackPane {
|
||||
@FXML
|
||||
private void onCancel() {
|
||||
failed.run();
|
||||
closeConsumer.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,10 @@ 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.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
@@ -40,6 +41,9 @@ 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.ui.FXUtils.installTooltip;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class AccountPage extends StackPane implements DecoratorPage {
|
||||
@@ -73,22 +77,20 @@ 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) {
|
||||
lblServer.setText(((AuthlibInjectorAccount) account).getServer().getName());
|
||||
FXUtils.setLimitHeight(this, 182);
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
|
||||
lblServer.setText(server.getName());
|
||||
installTooltip(lblServer, server.getUrl());
|
||||
} else {
|
||||
componentList.removeChildren(paneServer);
|
||||
|
||||
if (account instanceof OfflineAccount) {
|
||||
componentList.removeChildren(paneEmail);
|
||||
FXUtils.setLimitHeight(this, 110);
|
||||
} else
|
||||
FXUtils.setLimitHeight(this, 145);
|
||||
}
|
||||
}
|
||||
|
||||
btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
@@ -19,45 +19,47 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
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.ConfigHolder;
|
||||
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.DialogCloseEvent;
|
||||
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.onInvalidating;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AddAccountPane extends StackPane {
|
||||
@@ -73,46 +75,49 @@ public class AddAccountPane extends StackPane {
|
||||
@FXML private JFXDialogLayout layout;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private SpinnerPane acceptPane;
|
||||
private final Consumer<Region> finalization;
|
||||
|
||||
public AddAccountPane(Consumer<Region> finalization) {
|
||||
this.finalization = finalization;
|
||||
|
||||
public AddAccountPane() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
|
||||
|
||||
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
||||
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
||||
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
|
||||
Bindings.bindContent(cboServers.getItems(), ConfigHolder.CONFIG.authlibInjectorServers);
|
||||
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
|
||||
selectDefaultServer();
|
||||
|
||||
// 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.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
|
||||
txtPassword.setVisible(newValue.intValue() != 0);
|
||||
lblPassword.setVisible(newValue.intValue() != 0);
|
||||
cboServers.setVisible(newValue.intValue() == 2);
|
||||
linkManageInjectorServers.setVisible(newValue.intValue() == 2);
|
||||
lblInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
validateAcceptButton();
|
||||
});
|
||||
cboType.getItems().setAll(i18n("account.methods.offline"), i18n("account.methods.yggdrasil"), i18n("account.methods.authlib_injector"));
|
||||
cboType.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("@")));
|
||||
ReadOnlyIntegerProperty loginTypeIdProperty = cboType.getSelectionModel().selectedIndexProperty();
|
||||
|
||||
txtUsername.textProperty().addListener(it -> validateAcceptButton());
|
||||
txtPassword.textProperty().addListener(it -> validateAcceptButton());
|
||||
txtPassword.visibleProperty().bind(loginTypeIdProperty.isNotEqualTo(0));
|
||||
lblPassword.visibleProperty().bind(txtPassword.visibleProperty());
|
||||
|
||||
cboServers.visibleProperty().bind(loginTypeIdProperty.isEqualTo(2));
|
||||
lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
linkManageInjectorServers.visibleProperty().bind(cboServers.visibleProperty());
|
||||
|
||||
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
|
||||
btnAccept.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !txtUsername.validate() || (loginTypeIdProperty.get() != 0 && !txtPassword.validate()),
|
||||
txtUsername.textProperty(), txtPassword.textProperty(), loginTypeIdProperty));
|
||||
}
|
||||
|
||||
private void validateAcceptButton() {
|
||||
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
|
||||
/**
|
||||
* Selects the first server if no server is selected.
|
||||
*/
|
||||
private void selectDefaultServer() {
|
||||
if (!cboServers.getItems().isEmpty() && cboServers.getSelectionModel().isEmpty()) {
|
||||
cboServers.getSelectionModel().select(0);
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationAccept() {
|
||||
if (btnAccept.isDisabled())
|
||||
return;
|
||||
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
Object addtionalData;
|
||||
@@ -134,7 +139,7 @@ public class AddAccountPane extends StackPane {
|
||||
if (server.isPresent()) {
|
||||
addtionalData = server.get();
|
||||
} else {
|
||||
lblCreationWarning.setText(Launcher.i18n("account.failed.no_selected_server"));
|
||||
lblCreationWarning.setText(i18n("account.failed.no_selected_server"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
@@ -144,41 +149,35 @@ public class AddAccountPane extends StackPane {
|
||||
|
||||
acceptPane.showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
setDisable(true);
|
||||
|
||||
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);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
finalization.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
setDisable(false);
|
||||
acceptPane.hideSpinner();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationCancel() {
|
||||
finalization.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onManageInjecterServers() {
|
||||
finalization.accept(this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(Controllers.getServersPage());
|
||||
}
|
||||
|
||||
private void showSelector(Node node) {
|
||||
getChildren().setAll(node);
|
||||
}
|
||||
|
||||
private void closeSelector() {
|
||||
getChildren().setAll(layout);
|
||||
}
|
||||
|
||||
private class Selector extends BorderPane implements CharacterSelector {
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final JFXButton cancel = new JFXButton();
|
||||
@@ -189,11 +188,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);
|
||||
|
||||
@@ -235,7 +234,7 @@ public class AddAccountPane extends StackPane {
|
||||
listBox.add(accountItem);
|
||||
}
|
||||
|
||||
JFXUtilities.runInFX(() -> showSelector(this));
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(this));
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
@@ -243,35 +242,41 @@ public class AddAccountPane extends StackPane {
|
||||
if (selectedProfile == null)
|
||||
throw new NoSelectedCharacterException(account);
|
||||
|
||||
JFXUtilities.runInFX(AddAccountPane.this::closeSelector);
|
||||
|
||||
return selectedProfile;
|
||||
} catch (InterruptedException ignore) {
|
||||
throw new NoSelectedCharacterException(account);
|
||||
} finally {
|
||||
JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
return exception.getClass().getName() + ": " + 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
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.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private Label lblServerUrl;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblServerWarning;
|
||||
@FXML private JFXTextField txtServerUrl;
|
||||
@FXML private JFXDialogLayout addServerPane;
|
||||
@FXML private JFXDialogLayout confirmServerPane;
|
||||
@FXML private SpinnerPane nextPane;
|
||||
@FXML private JFXButton btnAddNext;
|
||||
|
||||
private TransitionHandler transitionHandler;
|
||||
|
||||
private AuthlibInjectorServer serverBeingAdded;
|
||||
|
||||
public AddAuthlibInjectorServerPane(String url) {
|
||||
this();
|
||||
txtServerUrl.setText(url);
|
||||
onAddNext();
|
||||
}
|
||||
|
||||
public AddAuthlibInjectorServerPane() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
|
||||
btnAddNext.disableProperty().bind(
|
||||
Bindings.createBooleanBinding(txtServerUrl::validate, txtServerUrl.textProperty()).not());
|
||||
nextPane.hideSpinner();
|
||||
}
|
||||
|
||||
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().getName() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddCancel() {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddNext() {
|
||||
if (btnAddNext.isDisabled())
|
||||
return;
|
||||
|
||||
lblCreationWarning.setText("");
|
||||
|
||||
String url = fixInputUrl(txtServerUrl.getText());
|
||||
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddPrev() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
if (!ConfigHolder.CONFIG.authlibInjectorServers.contains(serverBeingAdded)) {
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.add(serverBeingAdded);
|
||||
}
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,134 +1,69 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
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.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.NetworkUtils;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
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.manage.title"));
|
||||
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private Label lblServerUrl;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblServerWarning;
|
||||
@FXML private VBox listPane;
|
||||
@FXML private JFXTextField txtServerUrl;
|
||||
@FXML private JFXDialogLayout addServerPane;
|
||||
@FXML private JFXDialogLayout confirmServerPane;
|
||||
@FXML private JFXDialog dialog;
|
||||
@FXML private StackPane contentPane;
|
||||
@FXML private SpinnerPane nextPane;
|
||||
@FXML private JFXButton btnAddNext;
|
||||
|
||||
private final TransitionHandler transitionHandler;
|
||||
private InvalidationListener serversListener;
|
||||
|
||||
private AuthlibInjectorServer serverBeingAdded;
|
||||
public AuthlibInjectorServersPage() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
smoothScrolling(scrollPane);
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
|
||||
getChildren().remove(dialog);
|
||||
dialog.setDialogContainer(this);
|
||||
|
||||
txtServerUrl.textProperty().addListener((a, b, newValue) ->
|
||||
btnAddNext.setDisable(!txtServerUrl.validate()));
|
||||
|
||||
reload();
|
||||
serversListener = observable -> updateServersList();
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.addListener(new WeakInvalidationListener(serversListener));
|
||||
updateServersList();
|
||||
}
|
||||
|
||||
private void removeServer(AuthlibInjectorServerItem item) {
|
||||
Settings.SETTINGS.authlibInjectorServers.remove(item.getServer());
|
||||
reload();
|
||||
}
|
||||
|
||||
private void reload() {
|
||||
private void updateServersList() {
|
||||
listPane.getChildren().setAll(
|
||||
Settings.SETTINGS.authlibInjectorServers.stream()
|
||||
.map(server -> new AuthlibInjectorServerItem(server, this::removeServer))
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.stream()
|
||||
.map(server -> new AuthlibInjectorServerItem(server,
|
||||
item -> ConfigHolder.CONFIG.authlibInjectorServers.remove(item.getServer())))
|
||||
.collect(toList()));
|
||||
if (Settings.SETTINGS.authlibInjectorServers.isEmpty()) {
|
||||
onAdd();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
txtServerUrl.setText("");
|
||||
txtServerUrl.resetValidation();
|
||||
lblCreationWarning.setText("");
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddCancel() {
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddNext() {
|
||||
String url = fixInputUrl(txtServerUrl.getText());
|
||||
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.of(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
nextPane.hideSpinner();
|
||||
addServerPane.setDisable(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(serverBeingAdded.getName());
|
||||
lblServerUrl.setText(serverBeingAdded.getUrl());
|
||||
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddPrev() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
if (!Settings.INSTANCE.SETTINGS.authlibInjectorServers.contains(serverBeingAdded)) {
|
||||
Settings.INSTANCE.SETTINGS.authlibInjectorServers.add(serverBeingAdded);
|
||||
}
|
||||
reload();
|
||||
dialog.close();
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
@@ -143,19 +78,4 @@ 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 Launcher.i18n("account.failed.connect_injector_server");
|
||||
} else {
|
||||
return exception.getClass() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,12 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
@@ -132,15 +134,17 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type, Runnable onAccept) {
|
||||
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, type, onAccept));
|
||||
dialog(new MessageDialogPane(text, title, type, onAccept));
|
||||
}
|
||||
|
||||
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, onAccept, onCancel));
|
||||
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
||||
}
|
||||
|
||||
public static void inputDialog(String text, Consumer<String> onResult) {
|
||||
dialog(new InputDialogPane(text, Controllers::closeDialog, onResult));
|
||||
public static InputDialogPane inputDialog(String text, FutureCallback<String> onResult) {
|
||||
InputDialogPane pane = new InputDialogPane(text, onResult);
|
||||
dialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||
@@ -156,8 +160,12 @@ public final class Controllers {
|
||||
return pane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use {@link DialogCloseEvent}
|
||||
*/
|
||||
public static void closeDialog(Region content) {
|
||||
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
decorator.closeDialog(content);
|
||||
}
|
||||
|
||||
@@ -174,6 +182,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,12 +22,17 @@ 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;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.BoundingBox;
|
||||
import javafx.geometry.Bounds;
|
||||
@@ -38,6 +43,8 @@ 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.DragEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
@@ -45,7 +52,9 @@ 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.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -53,14 +62,16 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
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.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
||||
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.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
@@ -75,6 +86,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE),
|
||||
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
|
||||
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Decorator.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final ObjectProperty<Runnable> onCloseButtonAction;
|
||||
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
|
||||
|
||||
@@ -83,13 +96,14 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
private final boolean max, min;
|
||||
private final WizardController wizardController = new WizardController(this);
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
private final JFXDialog dialog;
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging, maximized;
|
||||
private BoundingBox originalBox, maximizedBox;
|
||||
private final TransitionHandler animationHandler;
|
||||
private final StackContainerPane dialogPane = new StackContainerPane();
|
||||
|
||||
private JFXDialog dialog;
|
||||
private StackContainerPane dialogPane;
|
||||
|
||||
@FXML
|
||||
private StackPane contentPlaceHolder;
|
||||
@@ -129,6 +143,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);
|
||||
@@ -166,11 +182,18 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
btnMax.fire();
|
||||
});
|
||||
|
||||
dialog = new JFXDialog();
|
||||
dialog.setOverlayClose(false);
|
||||
drawerWrapper.getChildren().add(0, dialog);
|
||||
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);
|
||||
@@ -188,6 +211,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
||||
|
||||
loadBackground();
|
||||
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
private void loadBackground() {
|
||||
@@ -542,20 +567,37 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
public void showDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialogPane.isEmpty())
|
||||
if (dialog == null) {
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
dialogPane.push(node);
|
||||
|
||||
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
|
||||
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
|
||||
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void closeDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
|
||||
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
|
||||
|
||||
dialogPane.pop(node);
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
Platform.runLater(() -> {
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
dialog.close();
|
||||
});
|
||||
|
||||
if (dialogPane.getChildren().isEmpty()) {
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider) {
|
||||
@@ -649,4 +691,10 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
public AdvancedListBox getLeftPane() {
|
||||
return leftPane;
|
||||
}
|
||||
|
||||
private void setupAuthlibInjectorDnD() {
|
||||
addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
|
||||
addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
|
||||
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public final class DialogController {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
JFXUtilities.runInFX(() -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, Controllers::closeDialog, it -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
}, latch::countDown);
|
||||
|
||||
@@ -45,9 +45,8 @@ import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -204,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);
|
||||
|
||||
@@ -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;
|
||||
@@ -46,9 +45,11 @@ import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.ClassTitle;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
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 +62,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 ->
|
||||
@@ -100,7 +101,7 @@ public final class LeftPaneController {
|
||||
}
|
||||
|
||||
private void addNewAccount() {
|
||||
Controllers.dialog(new AddAccountPane(Controllers::closeDialog));
|
||||
Controllers.dialog(new AddAccountPane());
|
||||
}
|
||||
|
||||
private void onSelectedAccountChanged(Account newAccount) {
|
||||
@@ -122,7 +123,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 +144,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) {
|
||||
@@ -207,7 +208,7 @@ public final class LeftPaneController {
|
||||
}
|
||||
|
||||
public void showUpdate() {
|
||||
launcherSettingsItem.setText(Launcher.i18n("update.found"));
|
||||
launcherSettingsItem.setText(i18n("update.found"));
|
||||
launcherSettingsItem.setTextFill(Color.RED);
|
||||
}
|
||||
|
||||
@@ -228,10 +229,10 @@ public final class LeftPaneController {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
|
||||
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), () -> {
|
||||
Controllers.closeDialog(region.get());
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
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;
|
||||
@@ -42,12 +42,12 @@ import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
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 +57,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 +96,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 +118,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 +139,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 +159,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"), ""));
|
||||
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
|
||||
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);
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
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);
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
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);
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -187,10 +188,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 +215,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));
|
||||
|
||||
@@ -18,18 +18,20 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
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;
|
||||
@@ -39,12 +41,11 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty location;
|
||||
private final Profile profile;
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtProfileName;
|
||||
@FXML
|
||||
private FileItem gameDir;
|
||||
@FXML private JFXTextField txtProfileName;
|
||||
@FXML private FileItem gameDir;
|
||||
@FXML private JFXButton btnSave;
|
||||
@FXML private JFXButton btnDelete;
|
||||
@FXML private JFXCheckBox toggleUseRelativePath;
|
||||
|
||||
/**
|
||||
* @param profile null if creating a new profile.
|
||||
@@ -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(""));
|
||||
|
||||
@@ -64,13 +65,16 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
gameDir.setProperty(location);
|
||||
gameDir.pathProperty().bindBidirectional(location);
|
||||
FXUtils.onChangeAndOperate(location, it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
|
||||
if (profile == null)
|
||||
gameDir.convertToRelativePathProperty().bind(toggleUseRelativePath.selectedProperty());
|
||||
if (profile == null) {
|
||||
btnDelete.setVisible(false);
|
||||
} else {
|
||||
toggleUseRelativePath.setSelected(profile.isUseRelativePath());
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
@@ -85,13 +89,17 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
private void onSave() {
|
||||
if (profile != null) {
|
||||
profile.setName(txtProfileName.getText());
|
||||
if (StringUtils.isNotBlank(getLocation()))
|
||||
profile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
if (StringUtils.isNotBlank(getLocation())) {
|
||||
profile.setGameDir(new File(getLocation()));
|
||||
}
|
||||
} else {
|
||||
if (StringUtils.isBlank(getLocation())) {
|
||||
gameDir.onExplore();
|
||||
}
|
||||
Settings.INSTANCE.putProfile(new Profile(txtProfileName.getText(), new File(getLocation())));
|
||||
Profile newProfile = new Profile(txtProfileName.getText(), new File(getLocation()));
|
||||
newProfile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
Settings.INSTANCE.putProfile(newProfile);
|
||||
}
|
||||
|
||||
Settings.INSTANCE.onProfileLoading();
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import com.jfoenix.controls.*;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -42,6 +43,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 +53,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;
|
||||
@@ -175,14 +179,14 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
chkProxyAuthentication.selectedProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setHasProxyAuth(newValue));
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
|
||||
fileCommonLocation.pathProperty().bindBidirectional(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,9 +203,9 @@ 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.getCustomColors().setAll(Arrays.stream(Theme.VALUES).map(Theme::getColor).map(Color::web).collect(Collectors.toList()));
|
||||
picker.setCustomColorText(i18n("color.custom"));
|
||||
picker.setRecentColorsText(i18n("color.recent"));
|
||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||
picker.setOnAction(e -> {
|
||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
||||
Settings.INSTANCE.setTheme(theme);
|
||||
@@ -214,7 +218,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 +243,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
||||
@@ -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,15 +195,18 @@ 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, resolve, reject) -> {
|
||||
if (profile.getRepository().renameVersion(version, res)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
resolve.run();
|
||||
} else {
|
||||
reject.accept(i18n("version.manage.rename.fail"));
|
||||
}
|
||||
});
|
||||
}).setInitialText(version);
|
||||
}
|
||||
|
||||
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,12 +39,12 @@ 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -52,6 +52,7 @@ public final class VersionSettingsController {
|
||||
private VersionSetting lastVersionSetting = null;
|
||||
private Profile profile;
|
||||
private String versionId;
|
||||
private boolean javaItemsLoaded;
|
||||
|
||||
@FXML private VBox rootPane;
|
||||
@FXML private ScrollPane scroll;
|
||||
@@ -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);
|
||||
|
||||
@@ -89,6 +90,7 @@ public final class VersionSettingsController {
|
||||
(variables.<List<JavaVersion>>get("list")).stream()
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion))
|
||||
.collect(Collectors.toList()));
|
||||
javaItemsLoaded = true;
|
||||
initializeSelectedJava();
|
||||
});
|
||||
|
||||
@@ -96,19 +98,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;
|
||||
|
||||
@@ -117,6 +117,7 @@ public final class VersionSettingsController {
|
||||
gameDirItem.setDisable(profile.getRepository().isModpack(versionId));
|
||||
globalItem.setDisable(profile.getRepository().isModpack(versionId));
|
||||
|
||||
// unbind data fields
|
||||
if (lastVersionSetting != null) {
|
||||
FXUtils.unbindInt(txtWidth, lastVersionSetting.widthProperty());
|
||||
FXUtils.unbindInt(txtHeight, lastVersionSetting.heightProperty());
|
||||
@@ -136,6 +137,12 @@ public final class VersionSettingsController {
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
}
|
||||
|
||||
// unbind data fields
|
||||
globalItem.setToggleSelectedListener(null);
|
||||
javaItem.setToggleSelectedListener(null);
|
||||
gameDirItem.setToggleSelectedListener(null);
|
||||
|
||||
// bind new data fields
|
||||
FXUtils.bindInt(txtWidth, versionSetting.widthProperty());
|
||||
FXUtils.bindInt(txtHeight, versionSetting.heightProperty());
|
||||
FXUtils.bindInt(txtMaxMemory, versionSetting.maxMemoryProperty());
|
||||
@@ -173,6 +180,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 +191,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())
|
||||
@@ -204,23 +213,34 @@ public final class VersionSettingsController {
|
||||
|
||||
private void initializeSelectedJava() {
|
||||
if (lastVersionSetting == null
|
||||
|| !JavaVersion.getJREsImmediately().isPresent() /* JREs are still being loaded */) {
|
||||
|| !javaItemsLoaded /* JREs are still being loaded */) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean flag = false;
|
||||
Toggle defaultToggle = null;
|
||||
for (Toggle toggle : javaItem.getGroup().getToggles()) {
|
||||
if (toggle.getUserData() == Lang.invoke(lastVersionSetting::getJavaVersion)) {
|
||||
toggle.setSelected(true);
|
||||
flag = true;
|
||||
} else if (toggle.getUserData() == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = toggle;
|
||||
List<Toggle> toggles = javaItem.getGroup().getToggles();
|
||||
if ("Custom".equals(lastVersionSetting.getJava())) {
|
||||
toggles.stream()
|
||||
.filter(javaItem::isCustomToggle)
|
||||
.findFirst()
|
||||
.get().setSelected(true);
|
||||
} else {
|
||||
JavaVersion selectedJava;
|
||||
try {
|
||||
selectedJava = lastVersionSetting.getJavaVersion();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
|
||||
toggles.stream()
|
||||
.filter(it -> it.getUserData() == selectedJava)
|
||||
.findFirst()
|
||||
.orElseGet( // fallback to select current java
|
||||
() -> toggles.stream()
|
||||
.filter(it -> it.getUserData() == JavaVersion.fromCurrentEnvironment())
|
||||
.findFirst()
|
||||
.get())
|
||||
.setSelected(true);
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,18 +255,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);
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AnimationProducer {
|
||||
void init(AnimationHandler handler);
|
||||
|
||||
List<KeyFrame> animate(AnimationHandler handler);
|
||||
}
|
||||
@@ -20,17 +20,43 @@ package org.jackhuang.hmcl.ui.animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.scene.Node;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public enum ContainerAnimations {
|
||||
NONE(c -> Collections.emptyList()),
|
||||
NONE(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c -> Collections.emptyList()),
|
||||
/**
|
||||
* A fade between the old and new view
|
||||
*/
|
||||
FADE(c ->
|
||||
FADE(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
@@ -40,7 +66,12 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_IN(c ->
|
||||
ZOOM_IN(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
@@ -52,7 +83,12 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_OUT(c ->
|
||||
ZOOM_OUT(c -> {
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getPreviousNode().setTranslateY(0);
|
||||
c.getCurrentNode().setTranslateX(0);
|
||||
c.getCurrentNode().setTranslateY(0);
|
||||
}, c ->
|
||||
(Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
@@ -64,7 +100,14 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_LEFT(c ->
|
||||
SWIPE_LEFT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
@@ -75,7 +118,14 @@ public enum ContainerAnimations {
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_RIGHT(c ->
|
||||
SWIPE_RIGHT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
@@ -85,8 +135,18 @@ public enum ContainerAnimations {
|
||||
|
||||
private final AnimationProducer animationProducer;
|
||||
|
||||
ContainerAnimations(AnimationProducer animationProducer) {
|
||||
this.animationProducer = animationProducer;
|
||||
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
|
||||
this.animationProducer = new AnimationProducer() {
|
||||
@Override
|
||||
public void init(AnimationHandler handler) {
|
||||
init.accept(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<KeyFrame> animate(AnimationHandler handler) {
|
||||
return animationProducer.apply(handler);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public AnimationProducer getAnimationProducer() {
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
@@ -78,28 +79,38 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
|
||||
updateContent(newView);
|
||||
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> view.getChildren().remove(previousNode)));
|
||||
nowAnimation.play();
|
||||
animation = nowAnimation;
|
||||
transition.init(this);
|
||||
|
||||
// runLater or "init" will not work
|
||||
Platform.runLater(() -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(transition.animate(this));
|
||||
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
|
||||
view.setMouseTransparent(false);
|
||||
view.getChildren().remove(previousNode);
|
||||
}));
|
||||
nowAnimation.play();
|
||||
animation = nowAnimation;
|
||||
});
|
||||
}
|
||||
|
||||
private void updateContent(Node newView) {
|
||||
if (view.getWidth() > 0 && view.getHeight() > 0) {
|
||||
previousNode = currentNode;
|
||||
if (previousNode == null)
|
||||
previousNode = NULL;
|
||||
previousNode = EMPTY_PANE;
|
||||
} else
|
||||
previousNode = NULL;
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
if (previousNode == newView)
|
||||
previousNode = NULL;
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
view.setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
view.getChildren().setAll(previousNode, currentNode);
|
||||
}
|
||||
|
||||
private static final StackPane NULL = new StackPane();
|
||||
private final StackPane EMPTY_PANE = new StackPane();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* 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 org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventTarget;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
/**
|
||||
* Indicates a close operation on the dialog.
|
||||
*
|
||||
* @author yushijinhun
|
||||
* @see Controllers#dialog(Region)
|
||||
*/
|
||||
public class DialogCloseEvent extends Event {
|
||||
|
||||
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("CLOSE");
|
||||
|
||||
public DialogCloseEvent() {
|
||||
super(CLOSE);
|
||||
}
|
||||
|
||||
public DialogCloseEvent(Object source, EventTarget target) {
|
||||
super(source, target, CLOSE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,9 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.Property;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -26,46 +28,70 @@ 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.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class FileItem extends BorderPane {
|
||||
private Property<String> property;
|
||||
private final Label x = new Label();
|
||||
private final Label lblPath = new Label();
|
||||
|
||||
private final SimpleStringProperty name = new SimpleStringProperty(this, "name");
|
||||
private final SimpleStringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||
private final SimpleStringProperty path = new SimpleStringProperty(this, "path");
|
||||
private final SimpleBooleanProperty convertToRelativePath = new SimpleBooleanProperty(this, "convertToRelativePath");
|
||||
|
||||
public FileItem() {
|
||||
VBox left = new VBox();
|
||||
Label name = new Label();
|
||||
name.textProperty().bind(nameProperty());
|
||||
x.getStyleClass().addAll("subtitle-label");
|
||||
left.getChildren().addAll(name, x);
|
||||
lblPath.getStyleClass().addAll("subtitle-label");
|
||||
lblPath.textProperty().bind(path);
|
||||
left.getChildren().addAll(name, lblPath);
|
||||
setLeft(left);
|
||||
|
||||
JFXButton right = new JFXButton();
|
||||
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();
|
||||
tip.textProperty().bind(tooltipProperty());
|
||||
Tooltip.install(this, tip);
|
||||
|
||||
convertToRelativePath.addListener(onInvalidating(() -> path.set(processPath(path.get()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given path to absolute/relative(if possible) path according to {@link #convertToRelativePathProperty()}.
|
||||
*/
|
||||
private String processPath(String path) {
|
||||
Path given = Paths.get(path).toAbsolutePath();
|
||||
if (isConvertToRelativePath()) {
|
||||
try {
|
||||
return Paths.get(".").toAbsolutePath().relativize(given).normalize().toString();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// the given path can't be relativized against current path
|
||||
}
|
||||
}
|
||||
return given.normalize().toString();
|
||||
}
|
||||
|
||||
public void onExplore() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
if (property.getValue() != null) {
|
||||
File file = new File(property.getValue());
|
||||
if (path.get() != null) {
|
||||
File file = new File(path.get());
|
||||
if (file.exists()) {
|
||||
if (file.isFile())
|
||||
file = file.getAbsoluteFile().getParentFile();
|
||||
@@ -76,16 +102,12 @@ public class FileItem extends BorderPane {
|
||||
}
|
||||
chooser.titleProperty().bind(titleProperty());
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null)
|
||||
property.setValue(selectedDir.getAbsolutePath());
|
||||
if (selectedDir != null) {
|
||||
path.set(processPath(selectedDir.toString()));
|
||||
}
|
||||
chooser.titleProperty().unbind();
|
||||
}
|
||||
|
||||
public void setProperty(Property<String> property) {
|
||||
this.property = property;
|
||||
x.textProperty().bind(property);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
@@ -121,4 +143,28 @@ public class FileItem extends BorderPane {
|
||||
public void setTooltip(String tooltip) {
|
||||
this.tooltip.set(tooltip);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path.get();
|
||||
}
|
||||
|
||||
public StringProperty pathProperty() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path.set(path);
|
||||
}
|
||||
|
||||
public boolean isConvertToRelativePath() {
|
||||
return convertToRelativePath.get();
|
||||
}
|
||||
|
||||
public BooleanProperty convertToRelativePathProperty() {
|
||||
return convertToRelativePath;
|
||||
}
|
||||
|
||||
public void setConvertToRelativePath(boolean convertToRelativePath) {
|
||||
this.convertToRelativePath.set(convertToRelativePath);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -19,13 +19,12 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
|
||||
public class InputDialogPane extends StackPane {
|
||||
|
||||
@@ -37,14 +36,28 @@ public class InputDialogPane extends StackPane {
|
||||
private JFXTextField textField;
|
||||
@FXML
|
||||
private Label content;
|
||||
@FXML
|
||||
private Label lblCreationWarning;
|
||||
|
||||
public InputDialogPane(String text, Consumer<Region> closeConsumer, Consumer<String> onResult) {
|
||||
public InputDialogPane(String text, FutureCallback<String> onResult) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
||||
content.setText(text);
|
||||
cancelButton.setOnMouseClicked(e -> closeConsumer.accept(this));
|
||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
onResult.accept(textField.getText());
|
||||
closeConsumer.accept(this);
|
||||
onResult.call(textField.getText(), () -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, msg -> {
|
||||
lblCreationWarning.setText(msg);
|
||||
});
|
||||
});
|
||||
|
||||
acceptButton.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !textField.validate(),
|
||||
textField.textProperty()
|
||||
));
|
||||
}
|
||||
|
||||
public void setInitialText(String text) {
|
||||
textField.setText(text);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -21,18 +21,16 @@ import com.jfoenix.controls.JFXButton;
|
||||
import javafx.fxml.FXML;
|
||||
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;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
private boolean closingDialog = true;
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@@ -47,7 +45,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
@FXML
|
||||
private HBox actions;
|
||||
|
||||
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, int type, Runnable onAccept) {
|
||||
public MessageDialogPane(String text, String title, int type, Runnable onAccept) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
|
||||
if (title != null)
|
||||
@@ -55,7 +53,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
|
||||
content.setText(text);
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
closeConsumer.accept(MessageDialogPane.this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
|
||||
});
|
||||
|
||||
@@ -82,22 +80,18 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, closeConsumer, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
|
||||
cancelButton.setVisible(true);
|
||||
cancelButton.setOnMouseClicked(e -> {
|
||||
closeConsumer.accept(MessageDialogPane.this);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
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);
|
||||
}
|
||||
|
||||
public void disableClosingDialog() {
|
||||
closingDialog = false;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
+7
-6
@@ -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;
|
||||
|
||||
@@ -34,11 +35,11 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
|
||||
Controllers.closeDialog(it);
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
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");
|
||||
@@ -61,12 +62,12 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
Controllers.closeDialog(pane);
|
||||
pane.fireEvent(new DialogCloseEvent());
|
||||
if (success) {
|
||||
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;
|
||||
@@ -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,16 +76,16 @@ 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()));
|
||||
|
||||
@@ -96,7 +97,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
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,14 +119,14 @@ 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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackManager;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -93,7 +94,7 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
config.logLines.set(100);
|
||||
config.configurations.clear();
|
||||
|
||||
zip.putTextFile(Settings.GSON.toJson(config), Settings.SETTINGS_FILE_NAME);
|
||||
zip.putTextFile(config.toJson(), ConfigHolder.CONFIG_FILENAME);
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
|
||||
File bg = new File("bg").getAbsoluteFile();
|
||||
|
||||
@@ -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,11 +23,14 @@ 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.DialogCloseEvent;
|
||||
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;
|
||||
@@ -112,13 +115,13 @@ 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();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
@@ -130,13 +133,13 @@ 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();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
@@ -152,7 +155,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();
|
||||
@@ -182,7 +185,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
|
||||
@@ -232,7 +235,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,14 @@ 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.ui.construct.DialogCloseEvent;
|
||||
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 +35,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 +58,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())
|
||||
@@ -72,7 +73,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
}
|
||||
|
||||
private static String getRealPath() {
|
||||
|
||||
@@ -29,8 +29,8 @@ import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
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;
|
||||
@@ -92,7 +92,7 @@ public final class UpdateChecker {
|
||||
if (value == null) {
|
||||
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)
|
||||
|
||||
@@ -21,9 +21,9 @@ 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;
|
||||
@@ -41,19 +41,17 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
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("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("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("com.sun.javafx.css.StyleManager.findMatchingStyles", i18n("launcher.update_java"));
|
||||
put("NoSuchAlgorithmException", "Has your operating system been installed completely or is a ghost system?");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #283593;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: white;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #43A047;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: black;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #E67E22;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: black;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #9C27B0;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: white;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
.root {
|
||||
-fx-base-color: #F44336;
|
||||
-fx-base-check-color: derive(-fx-base-color, 30%);
|
||||
-fx-base-text-fill: black;
|
||||
}
|
||||
@@ -569,7 +569,6 @@
|
||||
.list-cell:selected, .jfx-list-cell:selected {
|
||||
-fx-background-insets: 0.0;
|
||||
-fx-text-fill: BLACK;
|
||||
-fx-background-color: black;
|
||||
}
|
||||
|
||||
.jfx-list-cell:filled:hover,
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<Label fx:id="lblInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT"
|
||||
GridPane.columnIndex="0" GridPane.rowIndex="1"/>
|
||||
|
||||
<JFXComboBox fx:id="cboServers" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||
<JFXComboBox fx:id="cboServers" promptText="%general.prompt.empty" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||
|
||||
<Hyperlink fx:id="linkManageInjectorServers" text="%account.injector.manage"
|
||||
onMouseClicked="#onManageInjecterServers" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
|
||||
@@ -37,7 +37,7 @@
|
||||
<Label text="%account.username" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
|
||||
|
||||
<JFXTextField fx:id="txtUsername" GridPane.columnIndex="1" GridPane.rowIndex="2" GridPane.columnSpan="2"
|
||||
FXUtils.validateWhileTextChanged="true">
|
||||
FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
@@ -47,7 +47,7 @@
|
||||
<Label fx:id="lblPassword" text="%account.password" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
|
||||
|
||||
<JFXPasswordField fx:id="txtPassword" GridPane.columnIndex="1" GridPane.rowIndex="3"
|
||||
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true">
|
||||
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import com.jfoenix.controls.JFXProgressBar?>
|
||||
@@ -20,6 +21,9 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblType" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
@@ -33,6 +37,9 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblCharacter" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
@@ -46,6 +53,9 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblEmail" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
@@ -59,24 +69,26 @@
|
||||
</left>
|
||||
<right>
|
||||
<VBox>
|
||||
<BorderPane.margin>
|
||||
<Insets left="15.0" />
|
||||
</BorderPane.margin>
|
||||
<Label fx:id="lblServer" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</VBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</ComponentList>
|
||||
|
||||
<BorderPane pickOnBounds="false" style="-fx-padding: 4;">
|
||||
<left>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnRefresh" onMouseClicked="#onRefresh"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</VBox>
|
||||
<BorderPane pickOnBounds="false" style="-fx-padding: 4;">
|
||||
<left>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnRefresh" onMouseClicked="#onRefresh"
|
||||
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" visible="false" />
|
||||
</fx:root>
|
||||
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<StackPane fx:id="addServerContainer">
|
||||
<JFXDialogLayout fx:id="addServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url" onAction="#onAddNext">
|
||||
<validators>
|
||||
<URLValidator message="%input.url">
|
||||
<protocols>
|
||||
<String fx:value="http" />
|
||||
<String fx:value="https" />
|
||||
</protocols>
|
||||
</URLValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
</SpinnerPane>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
|
||||
<JFXDialogLayout fx:id="confirmServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints />
|
||||
</columnConstraints>
|
||||
<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="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" />
|
||||
</GridPane>
|
||||
</body>
|
||||
<actions>
|
||||
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -1,11 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
|
||||
<?import java.lang.String?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
<?import com.jfoenix.controls.*?>
|
||||
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@@ -23,61 +21,4 @@
|
||||
</JFXButton>
|
||||
</VBox>
|
||||
</StackPane>
|
||||
|
||||
<JFXDialog fx:id="dialog">
|
||||
<StackPane>
|
||||
<StackPane fx:id="addServerContainer">
|
||||
<JFXDialogLayout fx:id="addServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url">
|
||||
<validators>
|
||||
<URLValidator message="%input.url">
|
||||
<protocols>
|
||||
<String fx:value="http" />
|
||||
<String fx:value="https" />
|
||||
</protocols>
|
||||
</URLValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
</SpinnerPane>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
|
||||
<JFXDialogLayout fx:id="confirmServerPane">
|
||||
<heading>
|
||||
<Label text="%account.injector.add" />
|
||||
</heading>
|
||||
<body>
|
||||
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints />
|
||||
</columnConstraints>
|
||||
<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="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" />
|
||||
</GridPane>
|
||||
</body>
|
||||
<actions>
|
||||
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
|
||||
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</StackPane>
|
||||
</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>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@@ -16,8 +17,11 @@
|
||||
<JFXTextField fx:id="textField" />
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning"/>
|
||||
<SpinnerPane fx:id="acceptPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="acceptButton" text="%button.ok" styleClass="dialog-accept"/>
|
||||
</SpinnerPane>
|
||||
<JFXButton fx:id="cancelButton" styleClass="dialog-cancel" text="%button.cancel" />
|
||||
<JFXButton fx:id="acceptButton" styleClass="dialog-accept" text="%button.ok" />
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
</fx:root>
|
||||
@@ -1,11 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXCheckBox?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import com.jfoenix.validation.RequiredFieldValidator?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.FileItem?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
@@ -32,6 +34,12 @@
|
||||
</BorderPane>
|
||||
|
||||
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%profile.instance_directory.choose"/>
|
||||
|
||||
<JFXCheckBox fx:id="toggleUseRelativePath" text="%profile.use_relative_path" StackPane.alignment="CENTER_LEFT">
|
||||
<StackPane.margin>
|
||||
<Insets left="-10"/>
|
||||
</StackPane.margin>
|
||||
</JFXCheckBox>
|
||||
</ComponentList>
|
||||
|
||||
</VBox>
|
||||
|
||||
@@ -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 |
@@ -38,11 +38,12 @@ account.failed.connect_authentication_server=Cannot connect to the authenticatio
|
||||
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 login.
|
||||
account.failed.no_charactor=No character in this account.
|
||||
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.add=Add an authentication server
|
||||
account.injector.manage=Manage authentication servers
|
||||
account.injector.manage.title=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_url=Server URL
|
||||
@@ -52,7 +53,7 @@ account.methods.authlib_injector=authlib-injector
|
||||
account.methods.no_method=No login method
|
||||
account.methods.offline=Offline
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.missing=Missing account
|
||||
account.missing=None
|
||||
account.password=Password
|
||||
account.username=Name
|
||||
|
||||
@@ -91,6 +92,9 @@ 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
|
||||
@@ -99,6 +103,8 @@ 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=Required field
|
||||
@@ -122,7 +128,7 @@ install.select=Select an operation
|
||||
install.success=Installed successfully
|
||||
|
||||
lang=English
|
||||
lang.default=Belong to OS language.
|
||||
lang.default=Use system language
|
||||
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
|
||||
@@ -173,7 +179,7 @@ message.unknown=Unknown
|
||||
|
||||
|
||||
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
|
||||
@@ -202,6 +208,7 @@ modpack.task.install.error=Failed to install the modpack. Maybe the files is inc
|
||||
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.
|
||||
@@ -233,6 +240,7 @@ profile.instance_directory.choose=Choose Game Directory
|
||||
profile.new=New Config
|
||||
profile.title=Game Directories
|
||||
profile.selected=Selected
|
||||
profile.use_relative_path=Use relative path for game directory if possible
|
||||
|
||||
selector.choose=Choose
|
||||
selector.choose_file=Select a file
|
||||
@@ -240,7 +248,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)
|
||||
@@ -300,8 +308,8 @@ settings.type.special=Specialized version settings(will not affect other version
|
||||
|
||||
update=Update
|
||||
update.failed=Failed to check for updates.
|
||||
update.found=Found Update!
|
||||
update.newest_version=Newest version: %s
|
||||
update.found=Update Available!
|
||||
update.newest_version=Latest 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.tooltip=Update
|
||||
@@ -317,11 +325,12 @@ 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.remove=Delete this version
|
||||
version.manage.remove=Delete this game
|
||||
version.manage.remove.confirm=Sure to remove version %s?
|
||||
version.manage.remove_libraries=Delete library files
|
||||
version.manage.rename=Rename this version
|
||||
version.manage.rename=Rename this game
|
||||
version.manage.rename.message=Please enter the new name
|
||||
version.manage.rename.fail=Failed to rename this game.
|
||||
version.settings=Settings
|
||||
version.update=Update
|
||||
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
# Hello Minecraft! Launcher.
|
||||
# Copyright (C) 2013 huangyuhui <[email protected]>
|
||||
#
|
||||
# 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 ([email protected])
|
||||
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.manage.title=認證服務器
|
||||
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=已選中
|
||||
profile.use_relative_path=若可能,遊戲目錄使用相對路徑
|
||||
|
||||
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.manage.rename.fail=重命名版本失敗,可能文件被佔用或者名字有特殊字符
|
||||
version.settings=遊戲設置
|
||||
version.update=更新
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失敗
|
||||
wizard.finish=完成
|
||||
wizard.next=下一步 >
|
||||
@@ -38,11 +38,12 @@ 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.manage.title=认证服务器
|
||||
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
|
||||
account.injector.server=认证服务器
|
||||
account.injector.server_url=服务器地址
|
||||
@@ -91,6 +92,9 @@ 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=游戏文件夹
|
||||
@@ -99,6 +103,8 @@ folder.resourcepacks=资源包文件夹
|
||||
folder.saves=存档文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
|
||||
general.prompt.empty=(无)
|
||||
|
||||
input.email=用户名必须是邮箱
|
||||
input.number=必须是数字
|
||||
input.not_empty=必填项
|
||||
@@ -173,7 +179,7 @@ message.unknown=未知
|
||||
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=选择要导入的游戏整合包文件,如果您希望更新整合包,请输入要更新的版本名
|
||||
modpack.choose=选择要安装的游戏整合包文件
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown(图片请用网络图)。
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
@@ -202,6 +208,7 @@ 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 整合包受支持。
|
||||
@@ -233,6 +240,7 @@ profile.instance_directory.choose=选择游戏路径
|
||||
profile.new=新建配置
|
||||
profile.title=游戏目录
|
||||
profile.selected=已选中
|
||||
profile.use_relative_path=若可能,游戏目录使用相对路径
|
||||
|
||||
selector.choose=选择
|
||||
selector.choose_file=选择文件
|
||||
@@ -253,7 +261,7 @@ 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_common=不使用公共路径(游戏依赖文件存放在公共路径)
|
||||
settings.advanced.no_jvm_args=不添加默认的 JVM 参数(使用 Java 9 时必勾)
|
||||
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
|
||||
settings.advanced.server_ip=直入服务器 IP 地址(不必填写,启动游戏后直接进入对应服务器)
|
||||
@@ -322,6 +330,7 @@ version.manage.remove.confirm=真的要删除版本 %s 吗?
|
||||
version.manage.remove_libraries=删除所有库文件
|
||||
version.manage.rename=重命名该版本
|
||||
version.manage.rename.message=请输入要改成的名字
|
||||
version.manage.rename.fail=重命名版本失败,可能文件被占用或者名字有特殊字符
|
||||
version.settings=游戏设置
|
||||
version.update=更新
|
||||
|
||||
|
||||
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.
|
||||
|
||||
+21
-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
|
||||
@@ -18,8 +18,25 @@
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
/**
|
||||
* throws if wrong password or logging fails for too many times.
|
||||
* @author huangyuhui
|
||||
* Thrown when the stored credentials has expired.
|
||||
* This exception indicates that a password login should be performed.
|
||||
*
|
||||
* @author yushijinhun
|
||||
* @see Account#logIn()
|
||||
*/
|
||||
public final class InvalidCredentialsException 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,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 {
|
||||
|
||||
+42
-21
@@ -31,19 +31,23 @@ 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 AuthlibInjectorServer server;
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||
private AuthlibInjectorServer server;
|
||||
private ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader;
|
||||
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, AuthlibInjectorServer server, 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.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@@ -57,25 +61,42 @@ 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(server.getUrl()));
|
||||
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() + "=" + server.getUrl());
|
||||
|
||||
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
|
||||
|
||||
+23
-5
@@ -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;
|
||||
@@ -7,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -15,14 +33,14 @@ 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;
|
||||
|
||||
/**
|
||||
* @param serverLookup a function that looks up {@link AuthlibInjectorServer} by url
|
||||
*/
|
||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier, Function<String, AuthlibInjectorServer> serverLookup) {
|
||||
this.injectorJarPathSupplier = injectorJarPathSupplier;
|
||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader, Function<String, AuthlibInjectorServer> serverLookup) {
|
||||
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||
this.serverLookup = serverLookup;
|
||||
}
|
||||
|
||||
@@ -36,7 +54,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
|
||||
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
server, injectorJarPathSupplier, username, null, null);
|
||||
server, authlibInjectorDownloader, username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
@@ -56,6 +74,6 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
|
||||
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
server, injectorJarPathSupplier, username, session.getSelectedProfile().getId(), session);
|
||||
server, authlibInjectorDownloader, username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
}
|
||||
+23
-17
@@ -15,30 +15,36 @@
|
||||
* 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;
|
||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
|
||||
public class I18nException extends Exception {
|
||||
private final String localizedMessage;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public I18nException(String localizedMessage) {
|
||||
this.localizedMessage = localizedMessage;
|
||||
public class AuthlibInjectorArtifactInfo {
|
||||
|
||||
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 I18nException(String localizedMessage, Throwable suppressed) {
|
||||
addSuppressed(suppressed);
|
||||
public int getBuildNumber() {
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
this.localizedMessage = localizedMessage;
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public Path getLocation() {
|
||||
return location;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLocalizedMessage() {
|
||||
return localizedMessage;
|
||||
}
|
||||
|
||||
public static String getStackTrace(Throwable e) {
|
||||
if (e instanceof I18nException)
|
||||
return e.getLocalizedMessage();
|
||||
else
|
||||
return StringUtils.getStackTrace(e);
|
||||
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";
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.TransferMode;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
* @see https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#dnd-%E6%96%B9%E5%BC%8F%E6%B7%BB%E5%8A%A0-yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF
|
||||
*/
|
||||
public final class AuthlibInjectorDnD {
|
||||
|
||||
private static final String SCHEME = "authlib-injector";
|
||||
private static final String PATH_YGGDRASIL_SERVER = "yggdrasil-server";
|
||||
|
||||
private AuthlibInjectorDnD() {}
|
||||
|
||||
public static Optional<String> parseUrlFromDragboard(Dragboard dragboard) {
|
||||
String uri = dragboard.getString();
|
||||
if (uri == null) return Optional.empty();
|
||||
|
||||
String[] uriElements = uri.split(":");
|
||||
if (uriElements.length == 3 && SCHEME.equals(uriElements[0]) && PATH_YGGDRASIL_SERVER.equals(uriElements[1])) {
|
||||
try {
|
||||
return Optional.of(URLDecoder.decode(uriElements[2], "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static EventHandler<DragEvent> dragOverHandler() {
|
||||
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
|
||||
event.acceptTransferModes(TransferMode.COPY);
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
public static EventHandler<DragEvent> dragDroppedHandler(Consumer<String> onUrlTransfered) {
|
||||
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
|
||||
event.setDropCompleted(true);
|
||||
event.consume();
|
||||
onUrlTransfered.accept(url);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+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;
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ import org.jackhuang.hmcl.util.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
public class AuthlibInjectorServer {
|
||||
|
||||
@@ -36,7 +36,7 @@ public class AuthlibInjectorServer {
|
||||
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) {
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed response", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
|
||||
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven")
|
||||
.replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
|
||||
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven");
|
||||
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven")
|
||||
.replace("https://authlib-injector.yushi.moe", "https://bmclapi2.bangbang93.com/mirrors/authlib-injector");
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user