Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
db29f97397 | ||
|
|
573f41611d | ||
|
|
28ed103ec0 | ||
|
|
5e37750f00 | ||
|
|
5fe3cd9406 | ||
|
|
b00cc99408 | ||
|
|
9a66c3bba9 | ||
|
|
546921b445 | ||
|
|
8b698d3a4c | ||
|
|
c4608a949b | ||
|
|
bba1c559a3 | ||
|
|
bdee9ea72f | ||
|
|
520923ba08 | ||
|
|
936a74597f | ||
|
|
97132745cc | ||
|
|
2bb30f65f3 | ||
|
|
ec04d55793 | ||
|
|
88671db566 | ||
|
|
538f501bc0 | ||
|
|
3b75d41a2f | ||
|
|
110149d0e6 | ||
|
|
89b68781fa | ||
|
|
4cdc54b9c1 | ||
|
|
7673595a43 | ||
|
|
76a259c107 | ||
|
|
5f40d01fb4 | ||
|
|
df29d47c0a | ||
|
|
10d4084dde | ||
|
|
ba8cdb1b42 | ||
|
|
22732fee6a | ||
|
|
bfbc4613b9 | ||
|
|
92e5eecaf2 | ||
|
|
593159f4a6 | ||
|
|
53cad0e56a | ||
|
|
d61006dd67 | ||
|
|
eb565285b2 | ||
|
|
630f164877 | ||
|
|
38c6bce2c0 | ||
|
|
034734ec66 | ||
|
|
d875eeb22a | ||
|
|
61db6b8fb2 | ||
|
|
945d4cf20d | ||
|
|
631337c451 | ||
|
|
4f51e377ae | ||
|
|
e71c06a0d0 | ||
|
|
13aabde5d9 | ||
|
|
e3f371f457 | ||
|
|
6ee99b5736 | ||
|
|
895188b25f | ||
|
|
9fba923a51 | ||
|
|
37a39098f8 | ||
|
|
6fb548e6c3 | ||
|
|
110b7cb06f | ||
|
|
e9070a77c3 | ||
|
|
475e6e5511 | ||
|
|
eed4c7c11a | ||
|
|
d986d7ac03 | ||
|
|
f640922a50 | ||
|
|
118a6cf0d1 | ||
|
|
72871e6d9a | ||
|
|
61acbcec0d | ||
|
|
06af79e2b4 | ||
|
|
6782602141 | ||
|
|
8df17ae38a | ||
|
|
1645a93adf | ||
|
|
6a04ab37d7 | ||
|
|
27c9abd9ed | ||
|
|
a08be2e11d | ||
|
|
17615b1791 | ||
|
|
d75c3b584a | ||
|
|
20f919b93e | ||
|
|
20244fe5cc | ||
|
|
6a89f0a661 | ||
|
|
3a9efdb03e | ||
|
|
2ae00db94b | ||
|
|
a9261b759b | ||
|
|
6b02dc3cd5 | ||
|
|
afa37c0d2f | ||
|
|
2575f10497 | ||
|
|
27c4a68a13 | ||
|
|
f09cb54894 | ||
|
|
5431cfef79 | ||
|
|
35c0f3cddc | ||
|
|
efe22ca613 | ||
|
|
3408a8ffcf | ||
|
|
60a140efb3 | ||
|
|
bbe584bd91 | ||
|
|
3866377a28 | ||
|
|
ebc0b4b2b0 | ||
|
|
d6c65a320d | ||
|
|
2e46d76165 | ||
|
|
23037da406 | ||
|
|
617261c74d | ||
|
|
6c0d6ee0cf | ||
|
|
663e1ef7ab | ||
|
|
1a47d355f0 | ||
|
|
e407994103 | ||
|
|
bda2149c02 | ||
|
|
b925423d61 | ||
|
|
f3906a37bf | ||
|
|
a1e115ea6b | ||
|
|
03383510ef | ||
|
|
e8cf9644eb | ||
|
|
dbfcf1facc |
No files matched your search
+5
-5
@@ -48,16 +48,16 @@ jar {
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
|
||||
manifest {
|
||||
attributes 'Created-By' : 'Copyright(c) 2013-2017 huangyuhui.',
|
||||
attributes 'Created-By' : 'Copyright(c) 2013-2018 huangyuhui.',
|
||||
'Main-Class' : mainClass,
|
||||
'Multi-Release': "true"
|
||||
}
|
||||
|
||||
doLast {
|
||||
new File("build/signed").mkdirs()
|
||||
ant.signjar(signedjar: archivePath, jar: archivePath,
|
||||
keystore: "HMCL.keystore", storepass: "123456",
|
||||
alias: "HMCL")
|
||||
//new File("build/signed").mkdirs()
|
||||
//ant.signjar(signedjar: archivePath, jar: archivePath,
|
||||
// keystore: "HMCL.keystore", storepass: "123456",
|
||||
// alias: "HMCL")
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
archivePath.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
|
||||
@@ -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;
|
||||
@@ -31,10 +31,13 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class Launcher extends Application {
|
||||
|
||||
@@ -66,14 +69,21 @@ public final class Launcher extends Application {
|
||||
public static void main(String[] args) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
|
||||
if (!FileUtils.makeDirectory(LOG_DIRECTORY))
|
||||
System.out.println("Unable to create log directory " + LOG_DIRECTORY + ", log files cannot be generated.");
|
||||
|
||||
try {
|
||||
Logging.start();
|
||||
Logging.start(LOG_DIRECTORY);
|
||||
|
||||
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
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.
|
||||
@@ -108,45 +118,37 @@ public final class Launcher extends Application {
|
||||
});
|
||||
}
|
||||
|
||||
public static File getWorkingDirectory(String folder) {
|
||||
String home = System.getProperty("user.home", ".");
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case LINUX:
|
||||
return new File(home, "." + folder + "/");
|
||||
case WINDOWS:
|
||||
String appdata = System.getenv("APPDATA");
|
||||
return new File(Lang.nonNull(appdata, home), "." + folder + "/");
|
||||
case OSX:
|
||||
return new File(home, "Library/Application Support/" + folder);
|
||||
default:
|
||||
return new File(home, folder + "/");
|
||||
public static List<File> getCurrentJarFiles() {
|
||||
List<File> result = new LinkedList<>();
|
||||
if (Launcher.class.getClassLoader() instanceof URLClassLoader) {
|
||||
URL[] urls = ((URLClassLoader) Launcher.class.getClassLoader()).getURLs();
|
||||
for (URL u : urls)
|
||||
try {
|
||||
File f = new File(u.toURI());
|
||||
if (f.isFile() && (f.getName().endsWith(".exe") || f.getName().endsWith(".jar")))
|
||||
result.add(f);
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty())
|
||||
return null;
|
||||
else
|
||||
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 final File MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
public static final File HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
public static final File LOG_DIRECTORY = new File(Launcher.HMCL_DIRECTORY, "logs");
|
||||
|
||||
public static String i18n(String key, Object... formatArgs) {
|
||||
return String.format(i18n(key), formatArgs);
|
||||
}
|
||||
|
||||
public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft");
|
||||
public static final File HMCL_DIRECTORY = getWorkingDirectory("hmcl");
|
||||
|
||||
public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
|
||||
public static final String VERSION = System.getProperty("hmcl.version.override", "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@");
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION));
|
||||
public static final IUpgrader UPGRADER = new AppDataUpgrader();
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
|
||||
|
||||
public static final String CONTACT = "http://huangyuhui.duapp.com/hmcl.php";
|
||||
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,24 +17,86 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import javax.swing.*;
|
||||
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);
|
||||
}
|
||||
|
||||
private static void checkDirectoryPath() {
|
||||
String currentDirectory = new File("").getAbsolutePath();
|
||||
if (currentDirectory.contains("!")) {
|
||||
System.err.println("Exclamation mark(!) is not allowed in the path where HMCL is in. Forcibly exit.");
|
||||
|
||||
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
|
||||
String message = "Exclamation mark(!) is not allowed in the path where HMCL is in.\nThe path is " + currentDirectory;
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
System.exit(1);
|
||||
} else
|
||||
Launcher.main(args);
|
||||
showErrorAndExit("Exclamation mark(!) is not allowed in the path where HMCL is in.\n"
|
||||
+ "The path is " + currentDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkJavaFX() {
|
||||
try {
|
||||
Class.forName("javafx.application.Application");
|
||||
} catch (ClassNotFoundException e) {
|
||||
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.");
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* This event gets fired when added accounts.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class AccountAddedEvent extends Event {
|
||||
|
||||
private final Account account;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public AccountAddedEvent(Object source, Account account) {
|
||||
super(source);
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("account", account)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -34,13 +34,10 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
public final class AccountHelper {
|
||||
public static final AccountHelper INSTANCE = new AccountHelper();
|
||||
|
||||
private AccountHelper() {}
|
||||
|
||||
public static final File SKIN_DIR = new File(Launcher.HMCL_DIRECTORY, "skins");
|
||||
@@ -73,8 +70,8 @@ public final class AccountHelper {
|
||||
return new SkinLoadTask(account, proxy, true);
|
||||
}
|
||||
|
||||
private static File getSkinFile(String name) {
|
||||
return new File(SKIN_DIR, name + ".png");
|
||||
private static File getSkinFile(UUID uuid) {
|
||||
return new File(SKIN_DIR, uuid + ".png");
|
||||
}
|
||||
|
||||
public static Image getSkin(YggdrasilAccount account) {
|
||||
@@ -82,9 +79,11 @@ public final class AccountHelper {
|
||||
}
|
||||
|
||||
public static Image getSkin(YggdrasilAccount account, double scaleRatio) {
|
||||
if (account.getCharacter() == null)
|
||||
return getDefaultSkin(account, scaleRatio);
|
||||
File file = getSkinFile(account.getCharacter());
|
||||
UUID uuid = account.getUUID();
|
||||
if (uuid == null)
|
||||
return getSteveSkin(scaleRatio);
|
||||
|
||||
File file = getSkinFile(uuid);
|
||||
if (file.exists()) {
|
||||
Image original = new Image("file:" + file.getAbsolutePath());
|
||||
return new Image("file:" + file.getAbsolutePath(),
|
||||
@@ -92,15 +91,14 @@ public final class AccountHelper {
|
||||
original.getHeight() * scaleRatio,
|
||||
false, false);
|
||||
}
|
||||
return getDefaultSkin(account, scaleRatio);
|
||||
return getDefaultSkin(uuid, scaleRatio);
|
||||
}
|
||||
|
||||
public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio, Proxy proxy) throws Exception {
|
||||
String name = profile.getName();
|
||||
File file = getSkinFile(name);
|
||||
File file = getSkinFile(profile.getId());
|
||||
downloadSkin(account, profile, true, proxy);
|
||||
if (!file.exists())
|
||||
return getDefaultSkin(account, scaleRatio);
|
||||
return getDefaultSkin(profile.getId(), scaleRatio);
|
||||
|
||||
String url = "file:" + file.getAbsolutePath();
|
||||
return scale(url, scaleRatio);
|
||||
@@ -152,7 +150,7 @@ public final class AccountHelper {
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(profile.getName());
|
||||
File file = getSkinFile(profile.getId());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
@@ -165,7 +163,7 @@ public final class AccountHelper {
|
||||
Optional<Texture> texture = account.getSkin();
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(account.getCharacter());
|
||||
File file = getSkinFile(account.getUUID());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
@@ -187,11 +185,8 @@ public final class AccountHelper {
|
||||
return scale("/assets/img/alex.png", 4);
|
||||
}
|
||||
|
||||
public static Image getDefaultSkin(Account account, double scaleRatio) {
|
||||
if (account == null)
|
||||
return getSteveSkin(scaleRatio);
|
||||
|
||||
int type = account.getUUID().hashCode() & 1;
|
||||
public static Image getDefaultSkin(UUID uuid, double scaleRatio) {
|
||||
int type = uuid.hashCode() & 1;
|
||||
if (type == 1)
|
||||
return getAlexSkin(scaleRatio);
|
||||
else
|
||||
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -17,17 +17,18 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.ServerDisconnectException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
@@ -36,23 +37,28 @@ 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.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
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();
|
||||
private LauncherHelper(){}
|
||||
|
||||
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(() -> {});
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
|
||||
|
||||
public void launch(Profile profile, Account account, String selectedVersion, File scriptFile) {
|
||||
if (account == null)
|
||||
@@ -65,21 +71,23 @@ public final class LauncherHelper {
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
checkGameState(profile, setting, version, () -> Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile)));
|
||||
checkGameState(profile, setting, version, () -> {
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile));
|
||||
});
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void launch0(Profile profile, Account account, String selectedVersion, File scriptFile) {
|
||||
GameRepository repository = profile.getRepository();
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> Controllers.dialog(launchingStepsPane))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)))
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.then(variables -> {
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
@@ -87,37 +95,49 @@ public final class LauncherHelper {
|
||||
return dependencyManager.checkGameCompletionAsync(version);
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
|
||||
.then(new CurseCompletionTask(dependencyManager, selectedVersion))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(Launcher.i18n("account.methods"), variables -> {
|
||||
.then(var -> {
|
||||
try {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
} catch (ServerDisconnectException e) {
|
||||
if (account.canPlayOffline())
|
||||
variables.set("account", account.playOffline());
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if ("Curse".equals(configuration.getType()))
|
||||
return new CurseCompletionTask(dependencyManager, selectedVersion);
|
||||
else
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(i18n("account.methods"), variables -> {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
} catch (CredentialExpiredException e) {
|
||||
variables.set("account", DialogController.logIn(account));
|
||||
} catch (AuthenticationException e) {
|
||||
variables.set("account",
|
||||
account.playOffline().orElseThrow(() -> e));
|
||||
}
|
||||
}))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING)))
|
||||
.then(Task.of(variables -> {
|
||||
variables.set("launcher", new HMCLGameLauncher(
|
||||
repository, selectedVersion, variables.get("account"), setting.toLaunchOptions(profile.getGameDir()), new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent())
|
||||
repository,
|
||||
selectedVersion,
|
||||
variables.get("account"),
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
setting.getLauncherVisibility() == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent())
|
||||
));
|
||||
}))
|
||||
.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 -> {
|
||||
@@ -127,14 +147,14 @@ public final class LauncherHelper {
|
||||
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
|
||||
Launcher.stopApplication();
|
||||
else
|
||||
launchingStepsPane.setCancel(() -> {
|
||||
launchingStepsPane.setCancel(it -> {
|
||||
process.stop();
|
||||
Controllers.closeDialog();
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
} else
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog();
|
||||
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
|
||||
}))
|
||||
@@ -153,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();
|
||||
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, Controllers::closeDialog);
|
||||
// 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);
|
||||
@@ -170,47 +208,57 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
|
||||
boolean flag = false, suggest = true;
|
||||
boolean flag = false;
|
||||
|
||||
// Without onAccept called, the launching operation will be terminated.
|
||||
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
|
||||
JavaVersion java = setting.getJavaVersion();
|
||||
if (java == null) {
|
||||
Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
|
||||
// Minecraft 1.13 and later versions only support Java 8 or later.
|
||||
// Terminate launching operation.
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// LaunchWrapper will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
// cpw has claimed that he will make MinecraftForge of 1.13 and later versions able to run on Java 9.
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.java9"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
suggest = false;
|
||||
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.different_platform"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
if (suggest && Controllers.getDialogContent() instanceof MessageDialogPane)
|
||||
((MessageDialogPane) Controllers.getDialogContent()).disableClosingDialog();
|
||||
} else
|
||||
if (!flag)
|
||||
onAccept.run();
|
||||
}
|
||||
|
||||
@@ -220,8 +268,9 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE)
|
||||
Controllers.closeDialog();
|
||||
if (state == LoadingState.DONE) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
launchingStepsPane.setTitle(state.getLocalizedMessage());
|
||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
@@ -257,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
|
||||
@@ -300,10 +341,10 @@ public final class LauncherHelper {
|
||||
if (authInfo == null)
|
||||
forbiddenTokens = Collections.emptyMap();
|
||||
else
|
||||
forbiddenTokens = Lang.mapOf(
|
||||
new Pair<>(authInfo.getAccessToken(), "<access token>"),
|
||||
new Pair<>(authInfo.getUserId(), "<uuid>"),
|
||||
new Pair<>(authInfo.getUsername(), "<player>")
|
||||
forbiddenTokens = mapOf(
|
||||
pair(authInfo.getAccessToken(), "<access token>"),
|
||||
pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), "<uuid>"),
|
||||
pair(authInfo.getUsername(), "<player>")
|
||||
);
|
||||
|
||||
visibility = setting.getLauncherVisibility();
|
||||
@@ -333,7 +374,7 @@ public final class LauncherHelper {
|
||||
else
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(new Pair<>(log, level));
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > Settings.INSTANCE.getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
@@ -359,7 +400,9 @@ public final class LauncherHelper {
|
||||
// Never come to here.
|
||||
break;
|
||||
case KEEP:
|
||||
// No operations here
|
||||
Platform.runLater(() -> {
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
@@ -382,10 +425,10 @@ public final class LauncherHelper {
|
||||
|
||||
switch (exitType) {
|
||||
case JVM_ERROR:
|
||||
logWindow.setTitle(Launcher.i18n("launch.failed.cannot_create_jvm"));
|
||||
logWindow.setTitle(i18n("launch.failed.cannot_create_jvm"));
|
||||
break;
|
||||
case APPLICATION_ERROR:
|
||||
logWindow.setTitle(Launcher.i18n("launch.failed.exited_abnormally"));
|
||||
logWindow.setTitle(i18n("launch.failed.exited_abnormally"));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public enum LoadingState {
|
||||
DEPENDENCIES("launch.state.dependencies"),
|
||||
@@ -33,6 +33,6 @@ public enum LoadingState {
|
||||
}
|
||||
|
||||
public String getLocalizedMessage() {
|
||||
return Launcher.i18n(key);
|
||||
return i18n(key);
|
||||
}
|
||||
}
|
||||
@@ -59,12 +59,12 @@ public final class ModpackHelper {
|
||||
throw new UnsupportedModpackException(file.toString());
|
||||
}
|
||||
|
||||
public static <T> ModpackConfiguration<T> readModpackConfiguration(File file) throws IOException {
|
||||
public static ModpackConfiguration<?> readModpackConfiguration(File file) throws IOException {
|
||||
if (!file.exists())
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
else
|
||||
try {
|
||||
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<T>>() {
|
||||
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
|
||||
}.getType());
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
|
||||
@@ -22,23 +22,20 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorBuildInfo;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerResponse;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloader;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -50,14 +47,14 @@ public final class Accounts {
|
||||
public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil";
|
||||
public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector";
|
||||
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf(
|
||||
new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||
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(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.INSTANCE.getDownloadProvider())::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer))
|
||||
);
|
||||
|
||||
private static final Map<String, String> AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>();
|
||||
|
||||
public static String getAccountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY;
|
||||
else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY;
|
||||
@@ -73,33 +70,23 @@ public final class Accounts {
|
||||
return username + ":" + character;
|
||||
}
|
||||
|
||||
private static String downloadAuthlibInjector() throws Exception {
|
||||
AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo();
|
||||
File jar = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.jar");
|
||||
File local = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.txt");
|
||||
int buildNumber = 0;
|
||||
try {
|
||||
buildNumber = Integer.parseInt(FileUtils.readText(local));
|
||||
} catch (IOException | NumberFormatException ignore) {
|
||||
}
|
||||
if (buildNumber < buildInfo.getBuildNumber()) {
|
||||
new FileDownloadTask(new URL(buildInfo.getUrl()), jar).run();
|
||||
FileUtils.writeText(local, String.valueOf(buildInfo.getBuildNumber()));
|
||||
}
|
||||
return jar.getAbsolutePath();
|
||||
}
|
||||
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
|
||||
return ConfigHolder.CONFIG.authlibInjectorServers.stream()
|
||||
.filter(server -> url.equals(server.getUrl()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
// this usually happens when migrating data from an older version
|
||||
AuthlibInjectorServer server;
|
||||
try {
|
||||
server = AuthlibInjectorServer.fetchServerInfo(url);
|
||||
LOG.info("Migrated authlib injector server " + server);
|
||||
} catch (IOException e) {
|
||||
server = new AuthlibInjectorServer(url, url);
|
||||
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
|
||||
}
|
||||
|
||||
public static String getAuthlibInjectorServerName(String serverIp) throws Exception {
|
||||
if (AUTHLIB_INJECTOR_SERVER_NAMES.containsKey(serverIp))
|
||||
return AUTHLIB_INJECTOR_SERVER_NAMES.get(serverIp);
|
||||
else {
|
||||
AuthlibInjectorServerResponse response = Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(serverIp)), AuthlibInjectorServerResponse.class);
|
||||
AUTHLIB_INJECTOR_SERVER_NAMES.put(serverIp, response.getMeta().getServerName());
|
||||
return response.getMeta().getServerName();
|
||||
}
|
||||
}
|
||||
|
||||
public static TaskResult<String> getAuthlibInjectorServerNameAsync(AuthlibInjectorAccount account) {
|
||||
return Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(account.getServerBaseURL()));
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.add(server);
|
||||
return server;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,274 +17,124 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
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.util.JavaVersion;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.util.FileTypeAdapter;
|
||||
|
||||
import java.util.*;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
final class Config {
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
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")
|
||||
private String selectedProfile = "";
|
||||
public final StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("backgroundType")
|
||||
private int backgroundImageType = 0;
|
||||
public final IntegerProperty backgroundImageType = new SimpleIntegerProperty(0);
|
||||
|
||||
@SerializedName("bgpath")
|
||||
private String backgroundImage = null;
|
||||
public final StringProperty backgroundImage = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("commonpath")
|
||||
private String commonDirectory = Launcher.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||
public final StringProperty commonDirectory = new SimpleStringProperty(Launcher.MINECRAFT_DIRECTORY.getAbsolutePath());
|
||||
|
||||
@SerializedName("hasProxy")
|
||||
private boolean hasProxy = false;
|
||||
public final BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("hasProxyAuth")
|
||||
private boolean hasProxyAuth = false;
|
||||
public final BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("proxyType")
|
||||
private int proxyType = 0;
|
||||
public final IntegerProperty proxyType = new SimpleIntegerProperty();
|
||||
|
||||
@SerializedName("proxyHost")
|
||||
private String proxyHost = null;
|
||||
public final StringProperty proxyHost = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPort")
|
||||
private String proxyPort = null;
|
||||
public final StringProperty proxyPort = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyUserName")
|
||||
private String proxyUser = null;
|
||||
public final StringProperty proxyUser = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPassword")
|
||||
private String proxyPass = null;
|
||||
public final StringProperty proxyPass = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("theme")
|
||||
private String theme = null;
|
||||
|
||||
@SerializedName("java")
|
||||
private List<JavaVersion> java = null;
|
||||
public final StringProperty theme = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("localization")
|
||||
private String localization;
|
||||
public final StringProperty localization = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("downloadtype")
|
||||
private int downloadType = 0;
|
||||
public final IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
|
||||
@SerializedName("configurations")
|
||||
private Map<String, Profile> configurations = new TreeMap<>();
|
||||
public final ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
|
||||
@SerializedName("accounts")
|
||||
private List<Map<Object, Object>> accounts = new LinkedList<>();
|
||||
public final ObservableList<Map<Object, Object>> accounts = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("selectedAccount")
|
||||
private String selectedAccount = "";
|
||||
public final StringProperty selectedAccount = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("fontFamily")
|
||||
private String fontFamily = "Consolas";
|
||||
public final StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
|
||||
@SerializedName("fontSize")
|
||||
private double fontSize = 12;
|
||||
public final DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
|
||||
@SerializedName("logLines")
|
||||
private int logLines = 100;
|
||||
public final IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("authlibInjectorServerURLs")
|
||||
private Set<String> authlibInjectorServerURLs = new HashSet<>();
|
||||
@SerializedName("firstLaunch")
|
||||
public final BooleanProperty firstLaunch = new SimpleBooleanProperty(true);
|
||||
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
public String toJson() {
|
||||
return CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
public void setSelectedProfile(String selectedProfile) {
|
||||
this.selectedProfile = selectedProfile;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage = backgroundImage;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getBackgroundImageType() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(int backgroundImageType) {
|
||||
this.backgroundImageType = backgroundImageType;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
return commonDirectory;
|
||||
}
|
||||
|
||||
public void setCommonDirectory(String commonDirectory) {
|
||||
this.commonDirectory = commonDirectory;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public boolean isHasProxy() {
|
||||
return hasProxy;
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
this.hasProxy = hasProxy;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public boolean isHasProxyAuth() {
|
||||
return hasProxyAuth;
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
this.hasProxyAuth = hasProxyAuth;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getProxyType() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public void setProxyType(int proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
this.proxyHost = proxyHost;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
this.proxyPort = proxyPort;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return proxyUser;
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
this.proxyUser = proxyUser;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return proxyPass;
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
this.proxyPass = proxyPass;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public void setTheme(String theme) {
|
||||
this.theme = theme;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public List<JavaVersion> getJava() {
|
||||
return java;
|
||||
}
|
||||
|
||||
public void setJava(List<JavaVersion> java) {
|
||||
this.java = java;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public void setLocalization(String localization) {
|
||||
this.localization = localization;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadType) {
|
||||
this.downloadType = downloadType;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public Map<String, Profile> getConfigurations() {
|
||||
return configurations;
|
||||
}
|
||||
|
||||
public void setConfigurations(Map<String, Profile> configurations) {
|
||||
this.configurations = configurations;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public List<Map<Object, Object>> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void setAccounts(List<Map<Object, Object>> accounts) {
|
||||
this.accounts = accounts;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getSelectedAccount() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public void setSelectedAccount(String selectedAccount) {
|
||||
this.selectedAccount = selectedAccount;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily = fontFamily;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public void setFontSize(double fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return logLines;
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
this.logLines = logLines;
|
||||
}
|
||||
|
||||
public Set<String> getAuthlibInjectorServerURLs() {
|
||||
return authlibInjectorServerURLs;
|
||||
}
|
||||
|
||||
public void setAuthlibInjectorServerURLs(Set<String> authlibInjectorServerURLs) {
|
||||
this.authlibInjectorServerURLs = authlibInjectorServerURLs;
|
||||
Settings.INSTANCE.save();
|
||||
@Override
|
||||
public Config clone() {
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -116,6 +116,7 @@ public final class Profile {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
if (vs == null || vs.isUsesGlobal()) {
|
||||
getGlobal().setGlobal(true); // always keep global.isGlobal = true
|
||||
getGlobal().setUsesGlobal(true);
|
||||
return getGlobal();
|
||||
} else
|
||||
return vs;
|
||||
|
||||
@@ -17,18 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Profiles {
|
||||
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
private Profiles() {
|
||||
}
|
||||
|
||||
public static String getProfileDisplayName(Profile profile) {
|
||||
switch (profile.getName()) {
|
||||
case Settings.DEFAULT_PROFILE:
|
||||
return Launcher.i18n("profile.default");
|
||||
case Settings.HOME_PROFILE:
|
||||
return Launcher.i18n("profile.home");
|
||||
case Profiles.DEFAULT_PROFILE:
|
||||
return i18n("profile.default");
|
||||
case Profiles.HOME_PROFILE:
|
||||
return i18n("profile.home");
|
||||
default:
|
||||
return profile.getName();
|
||||
}
|
||||
|
||||
@@ -17,62 +17,55 @@
|
||||
*/
|
||||
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.scene.text.Font;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.event.AccountLoadingEvent;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
|
||||
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;
|
||||
import java.net.Proxy;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class Settings {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||
.setPrettyPrinting().create();
|
||||
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
public static final File SETTINGS_FILE = new File("hmcl.json").getAbsoluteFile();
|
||||
|
||||
public static final Settings INSTANCE = new Settings();
|
||||
|
||||
private Settings() {}
|
||||
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
|
||||
|
||||
private final Config SETTINGS = initSettings();
|
||||
private final boolean firstLaunch;
|
||||
|
||||
private final Map<String, Account> accounts = new HashMap<>();
|
||||
private Settings() {
|
||||
firstLaunch = ConfigHolder.CONFIG.firstLaunch.get();
|
||||
ConfigHolder.CONFIG.firstLaunch.set(false);
|
||||
|
||||
{
|
||||
loadProxy();
|
||||
|
||||
for (Iterator<Map<Object, Object>> iterator = SETTINGS.getAccounts().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(Lang.get(settings, "type", String.class).orElse(""));
|
||||
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
|
||||
if (factory == null) {
|
||||
// unrecognized account type, so remove it.
|
||||
LOG.warning("Unrecognized account type, removing: " + settings);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
@@ -81,7 +74,7 @@ public class Settings {
|
||||
try {
|
||||
account = factory.fromStorage(settings, getProxy());
|
||||
} catch (Exception e) {
|
||||
// storage is malformed, delete.
|
||||
LOG.log(Level.WARNING, "Malformed account storage, removing: " + settings, e);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
@@ -89,8 +82,10 @@ public class Settings {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
checkAuthlibInjectorServerURLs();
|
||||
checkAuthlibInjectorAccounts();
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
this.selectedAccount.set(accounts.get(ConfigHolder.CONFIG.selectedAccount.get()));
|
||||
|
||||
checkProfileMap();
|
||||
|
||||
save();
|
||||
@@ -104,46 +99,27 @@ public class Settings {
|
||||
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
|
||||
}
|
||||
|
||||
private 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.getAccounts().clear();
|
||||
for (Account account : accounts.values()) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", Accounts.getAccountType(account));
|
||||
SETTINGS.getAccounts().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.getCommonDirectory()) {
|
||||
public boolean isFirstLaunch() {
|
||||
return firstLaunch;
|
||||
}
|
||||
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", ConfigHolder.CONFIG.commonDirectory.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setCommonDirectory(get());
|
||||
ConfigHolder.CONFIG.commonDirectory.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -159,7 +135,7 @@ public class Settings {
|
||||
this.commonPath.set(commonPath);
|
||||
}
|
||||
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.getLocalization());
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(ConfigHolder.CONFIG.localization.get());
|
||||
|
||||
public Locales.SupportedLocale getLocale() {
|
||||
return locale;
|
||||
@@ -167,7 +143,8 @@ public class Settings {
|
||||
|
||||
public void setLocale(Locales.SupportedLocale locale) {
|
||||
this.locale = locale;
|
||||
SETTINGS.setLocalization(Locales.getNameByLocale(locale));
|
||||
ConfigHolder.CONFIG.localization.set(Locales.getNameByLocale(locale));
|
||||
save();
|
||||
}
|
||||
|
||||
private Proxy proxy = Proxy.NO_PROXY;
|
||||
@@ -176,7 +153,7 @@ public class Settings {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.getProxyType());
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(ConfigHolder.CONFIG.proxyType.get());
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType;
|
||||
@@ -184,49 +161,64 @@ public class Settings {
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
SETTINGS.setProxyType(Proxies.PROXIES.indexOf(proxyType));
|
||||
ConfigHolder.CONFIG.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
|
||||
save();
|
||||
loadProxy();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return SETTINGS.getProxyHost();
|
||||
return ConfigHolder.CONFIG.proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
SETTINGS.setProxyHost(proxyHost);
|
||||
ConfigHolder.CONFIG.proxyHost.set(proxyHost);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return SETTINGS.getProxyPort();
|
||||
return ConfigHolder.CONFIG.proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
SETTINGS.setProxyPort(proxyPort);
|
||||
ConfigHolder.CONFIG.proxyPort.set(proxyPort);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return SETTINGS.getProxyUser();
|
||||
return ConfigHolder.CONFIG.proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
SETTINGS.setProxyUser(proxyUser);
|
||||
ConfigHolder.CONFIG.proxyUser.set(proxyUser);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return SETTINGS.getProxyPass();
|
||||
return ConfigHolder.CONFIG.proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
SETTINGS.setProxyPass(proxyPass);
|
||||
ConfigHolder.CONFIG.proxyPass.set(proxyPass);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxy() { return SETTINGS.isHasProxy(); }
|
||||
public boolean hasProxy() {
|
||||
return ConfigHolder.CONFIG.hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) { SETTINGS.setHasProxy(hasProxy); }
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
ConfigHolder.CONFIG.hasProxy.set(hasProxy);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() { return SETTINGS.isHasProxyAuth(); }
|
||||
public boolean hasProxyAuth() {
|
||||
return ConfigHolder.CONFIG.hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) { SETTINGS.setHasProxyAuth(hasProxyAuth); }
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
ConfigHolder.CONFIG.hasProxyAuth.set(hasProxyAuth);
|
||||
save();
|
||||
}
|
||||
|
||||
private void loadProxy() {
|
||||
String host = getProxyHost();
|
||||
@@ -255,59 +247,39 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return Font.font(SETTINGS.getFontFamily(), SETTINGS.getFontSize());
|
||||
return Font.font(ConfigHolder.CONFIG.fontFamily.get(), ConfigHolder.CONFIG.fontSize.get());
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
SETTINGS.setFontFamily(font.getFamily());
|
||||
SETTINGS.setFontSize(font.getSize());
|
||||
ConfigHolder.CONFIG.fontFamily.set(font.getFamily());
|
||||
ConfigHolder.CONFIG.fontSize.set(font.getSize());
|
||||
save();
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return Math.max(SETTINGS.getLogLines(), 100);
|
||||
return Math.max(ConfigHolder.CONFIG.logLines.get(), 100);
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
SETTINGS.setLogLines(logLines);
|
||||
ConfigHolder.CONFIG.logLines.set(logLines);
|
||||
save();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* AUTHLIB INJECTORS *
|
||||
****************************************/
|
||||
|
||||
public Set<String> getAuthlibInjectorServerURLs() {
|
||||
return SETTINGS.getAuthlibInjectorServerURLs();
|
||||
}
|
||||
|
||||
public void removeAuthlibInjectorServerURL(String serverURL) {
|
||||
checkAuthlibInjectorServerURLs();
|
||||
|
||||
SETTINGS.getAuthlibInjectorServerURLs().remove(serverURL);
|
||||
|
||||
checkAuthlibInjectorAccounts();
|
||||
save();
|
||||
}
|
||||
|
||||
public void addAuthlibInjectorServerURL(String serverURL) {
|
||||
checkAuthlibInjectorServerURLs();
|
||||
|
||||
SETTINGS.getAuthlibInjectorServerURLs().add(serverURL);
|
||||
save();
|
||||
}
|
||||
|
||||
private void checkAuthlibInjectorServerURLs() {
|
||||
if (SETTINGS.getAuthlibInjectorServerURLs() == null)
|
||||
SETTINGS.setAuthlibInjectorServerURLs(new HashSet<>());
|
||||
}
|
||||
|
||||
private void checkAuthlibInjectorAccounts() {
|
||||
for (Account account : getAccounts()) {
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorAccount injectorAccount = (AuthlibInjectorAccount) account;
|
||||
if (!SETTINGS.getAuthlibInjectorServerURLs().contains(injectorAccount.getServerBaseURL()))
|
||||
deleteAccount(account);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
|
||||
* This method performs a check and removes the dangling accounts.
|
||||
* Don't call this before {@link #migrateAuthlibInjectorServers()} is called, otherwise old data would be lost.
|
||||
*/
|
||||
private void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.values().stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.filter(it -> !ConfigHolder.CONFIG.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
|
||||
.collect(toList())
|
||||
.forEach(this::deleteAccount);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
@@ -315,21 +287,22 @@ public class Settings {
|
||||
****************************************/
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
return DownloadProviders.getDownloadProvider(SETTINGS.getDownloadType());
|
||||
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.setDownloadType(index);
|
||||
ConfigHolder.CONFIG.downloadType.set(index);
|
||||
save();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* ACCOUNTS *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.getSelectedAccount())) {
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", null) {
|
||||
@Override
|
||||
public Account get() {
|
||||
Account a = super.get();
|
||||
@@ -351,7 +324,8 @@ public class Settings {
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setSelectedAccount(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
ConfigHolder.CONFIG.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -370,6 +344,8 @@ public class Settings {
|
||||
public void addAccount(Account account) {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
onAccountLoading();
|
||||
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountAddedEvent(this, account));
|
||||
}
|
||||
|
||||
public Account getAccount(String name, String character) {
|
||||
@@ -398,12 +374,13 @@ public class Settings {
|
||||
* BACKGROUND *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.getBackgroundImage()) {
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", ConfigHolder.CONFIG.backgroundImage.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setBackgroundImage(get());
|
||||
ConfigHolder.CONFIG.backgroundImage.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -419,12 +396,13 @@ public class Settings {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.getBackgroundImageType())) {
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(ConfigHolder.CONFIG.backgroundImageType.get())) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setBackgroundImageType(get().ordinal());
|
||||
ConfigHolder.CONFIG.backgroundImageType.set(get().ordinal());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -444,12 +422,13 @@ public class Settings {
|
||||
* THEME *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.getTheme()).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.setTheme(get().getName().toLowerCase());
|
||||
ConfigHolder.CONFIG.theme.set(get().getName().toLowerCase());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -472,16 +451,20 @@ public class Settings {
|
||||
public Profile getSelectedProfile() {
|
||||
checkProfileMap();
|
||||
|
||||
if (!hasProfile(SETTINGS.getSelectedProfile())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(SETTINGS::setSelectedProfile);
|
||||
if (!hasProfile(ConfigHolder.CONFIG.selectedProfile.get())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
|
||||
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile);
|
||||
save();
|
||||
});
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
return getProfile(SETTINGS.getSelectedProfile());
|
||||
return getProfile(ConfigHolder.CONFIG.selectedProfile.get());
|
||||
}
|
||||
|
||||
public void setSelectedProfile(Profile selectedProfile) {
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.getSelectedProfile())) {
|
||||
SETTINGS.setSelectedProfile(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);
|
||||
}
|
||||
}
|
||||
@@ -498,7 +481,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
return SETTINGS.getConfigurations();
|
||||
return ConfigHolder.CONFIG.configurations;
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
@@ -529,8 +512,8 @@ public class Settings {
|
||||
|
||||
private void checkProfileMap() {
|
||||
if (getProfileMap().isEmpty()) {
|
||||
getProfileMap().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE));
|
||||
getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
|
||||
getProfileMap().put(Profiles.DEFAULT_PROFILE, new Profile(Profiles.DEFAULT_PROFILE));
|
||||
getProfileMap().put(Profiles.HOME_PROFILE, new Profile(Profiles.HOME_PROFILE, Launcher.MINECRAFT_DIRECTORY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,4 +538,8 @@ public class Settings {
|
||||
public void onAccountLoading() {
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountLoadingEvent(this, getAccounts()));
|
||||
}
|
||||
|
||||
public Config getRawConfig() {
|
||||
return ConfigHolder.CONFIG.clone();
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,9 @@ import org.jackhuang.hmcl.util.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -87,6 +89,20 @@ public final class VersionSetting {
|
||||
javaProperty.set(java);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty defaultJavaPathProperty = new ImmediateStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
* Path to Java executable, or null if user customizes java directory.
|
||||
* It's used to determine which JRE to use when multiple JREs match the selected Java version.
|
||||
*/
|
||||
public String getDefaultJavaPath() {
|
||||
return defaultJavaPathProperty.get();
|
||||
}
|
||||
|
||||
public void setDefaultJavaPath(String defaultJavaPath) {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty javaDirProperty = new ImmediateStringProperty(this, "javaDir", "");
|
||||
|
||||
public ImmediateStringProperty javaDirProperty() {
|
||||
@@ -444,12 +460,18 @@ public final class VersionSetting {
|
||||
return null; // Custom Java Directory not found,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
JavaVersion c = JavaVersion.getJREs().get(getJava());
|
||||
if (c == null) {
|
||||
List<JavaVersion> matchedJava = JavaVersion.getJREs().stream()
|
||||
.filter(java -> java.getVersion().equals(getJava()))
|
||||
.collect(Collectors.toList());
|
||||
if (matchedJava.isEmpty()) {
|
||||
setJava("Default");
|
||||
return JavaVersion.fromCurrentEnvironment();
|
||||
} else
|
||||
return c;
|
||||
} else {
|
||||
return matchedJava.stream()
|
||||
.filter(java -> java.getBinary().toString().equals(getDefaultJavaPath()))
|
||||
.findFirst()
|
||||
.orElse(matchedJava.get(0));
|
||||
}
|
||||
} else throw new Error();
|
||||
}
|
||||
|
||||
@@ -475,6 +497,7 @@ public final class VersionSetting {
|
||||
gameDirTypeProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
}
|
||||
|
||||
public LaunchOptions toLaunchOptions(File gameDir) throws InterruptedException {
|
||||
@@ -535,6 +558,7 @@ public final class VersionSetting {
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
|
||||
obj.addProperty("defaultJavaPath", src.getDefaultJavaPath());
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -571,6 +595,7 @@ public final class VersionSetting {
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(1)]);
|
||||
vs.setGameDirType(EnumGameDirectory.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -28,6 +27,7 @@ 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;
|
||||
|
||||
@@ -41,7 +41,6 @@ public class AccountLoginPane extends StackPane {
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
private JFXDialog dialog;
|
||||
|
||||
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
this.oldAccount = oldAccount;
|
||||
@@ -69,9 +68,9 @@ public class AccountLoginPane extends StackPane {
|
||||
Object account = variable.get("login");
|
||||
if (account instanceof AuthInfo) {
|
||||
success.accept(((AuthInfo) account));
|
||||
dialog.close();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else if (account instanceof NoSelectedCharacterException) {
|
||||
dialog.close();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
|
||||
}
|
||||
@@ -83,10 +82,6 @@ public class AccountLoginPane extends StackPane {
|
||||
@FXML
|
||||
private void onCancel() {
|
||||
failed.run();
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
public void setDialog(JFXDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
}
|
||||
@@ -28,19 +28,22 @@ 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;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.installTooltip;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class AccountPage extends StackPane implements DecoratorPage {
|
||||
@@ -74,23 +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) {
|
||||
Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account)
|
||||
.subscribe(Schedulers.javafx(), variables -> lblServer.setText(variables.get("serverName")));
|
||||
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,9 +19,11 @@ 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;
|
||||
@@ -29,29 +31,36 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
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.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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class AddAccountPane extends StackPane {
|
||||
|
||||
@@ -60,109 +69,115 @@ public class AddAccountPane extends StackPane {
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblPassword;
|
||||
@FXML private JFXComboBox<String> cboType;
|
||||
@FXML private JFXComboBox<TwoLineListItem> cboServers;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
@FXML private Label lblAddInjectorServer;
|
||||
@FXML private Hyperlink linkAddInjectorServer;
|
||||
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
|
||||
@FXML private Label lblInjectorServer;
|
||||
@FXML private Hyperlink linkManageInjectorServers;
|
||||
@FXML private JFXDialogLayout layout;
|
||||
private final Runnable finalization;
|
||||
|
||||
public AddAccountPane(Runnable finalization) {
|
||||
this.finalization = finalization;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private SpinnerPane acceptPane;
|
||||
|
||||
public AddAccountPane() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
|
||||
|
||||
loadServers();
|
||||
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
||||
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
||||
Bindings.bindContent(cboServers.getItems(), ConfigHolder.CONFIG.authlibInjectorServers);
|
||||
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
|
||||
selectDefaultServer();
|
||||
|
||||
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);
|
||||
linkAddInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
lblAddInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
});
|
||||
cboType.getItems().setAll(i18n("account.methods.offline"), i18n("account.methods.yggdrasil"), i18n("account.methods.authlib_injector"));
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
// These two lines can eliminate black, don't know why.
|
||||
cboServers.getItems().setAll(new TwoLineListItem("", ""));
|
||||
cboServers.getSelectionModel().select(0);
|
||||
ReadOnlyIntegerProperty loginTypeIdProperty = cboType.getSelectionModel().selectedIndexProperty();
|
||||
|
||||
txtPassword.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
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 loadServers() {
|
||||
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
|
||||
.flatMap(serverURL -> {
|
||||
try {
|
||||
return Stream.of(new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL));
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()))
|
||||
.subscribe(Task.of(Schedulers.javafx(), variables -> {
|
||||
cboServers.getItems().setAll(variables.<Collection<TwoLineListItem>>get("list"));
|
||||
if (!cboServers.getItems().isEmpty())
|
||||
cboServers.getSelectionModel().select(0);
|
||||
}));
|
||||
/**
|
||||
* 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() {
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
if (btnAccept.isDisabled())
|
||||
return;
|
||||
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
String apiRoot = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()).map(TwoLineListItem::getSubtitle).orElse(null);
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult("create_account", () -> {
|
||||
AccountFactory<?> factory;
|
||||
switch (type) {
|
||||
case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break;
|
||||
case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break;
|
||||
case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break;
|
||||
default: throw new Error();
|
||||
}
|
||||
Object addtionalData;
|
||||
|
||||
return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy());
|
||||
}).finalized(Schedulers.javafx(), variables -> {
|
||||
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
||||
progressBar.setVisible(false);
|
||||
finalization.run();
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
finalization.run();
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
progressBar.setVisible(false);
|
||||
}).start();
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
AccountFactory<?> factory;
|
||||
switch (type) {
|
||||
case 0:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 1:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 2:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY);
|
||||
Optional<AuthlibInjectorServer> server = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem());
|
||||
if (server.isPresent()) {
|
||||
addtionalData = server.get();
|
||||
} else {
|
||||
lblCreationWarning.setText(i18n("account.failed.no_selected_server"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
acceptPane.showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
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();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
setDisable(false);
|
||||
acceptPane.hideSpinner();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationCancel() {
|
||||
finalization.run();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddInjecterServer() {
|
||||
finalization.run();
|
||||
private void onManageInjecterServers() {
|
||||
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();
|
||||
@@ -173,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);
|
||||
|
||||
@@ -198,13 +213,14 @@ public class AddAccountPane extends StackPane {
|
||||
try {
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy());
|
||||
} catch (Exception e) {
|
||||
image = FXUtils.DEFAULT_ICON;
|
||||
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
|
||||
image = null;
|
||||
}
|
||||
ImageView portraitView = new ImageView();
|
||||
portraitView.setSmooth(false);
|
||||
if (image == FXUtils.DEFAULT_ICON)
|
||||
portraitView.setImage(FXUtils.DEFAULT_ICON);
|
||||
else {
|
||||
if (image == null) {
|
||||
portraitView.setImage(Constants.DEFAULT_ICON.get());
|
||||
} else {
|
||||
portraitView.setImage(image);
|
||||
portraitView.setViewport(AccountHelper.getViewport(4));
|
||||
}
|
||||
@@ -218,7 +234,7 @@ public class AddAccountPane extends StackPane {
|
||||
listBox.add(accountItem);
|
||||
}
|
||||
|
||||
JFXUtilities.runInFX(() -> showSelector(this));
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(this));
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
@@ -226,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 <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,19 +23,19 @@ import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AuthlibInjectorServerItem extends BorderPane {
|
||||
private final AuthlibInjectorServerInfo info;
|
||||
private final AuthlibInjectorServer server;
|
||||
|
||||
private final Label lblServerName = new Label();
|
||||
private final Label lblServerIp = new Label();
|
||||
|
||||
public AuthlibInjectorServerItem(AuthlibInjectorServerInfo info, Consumer<AuthlibInjectorServerItem> deleteCallback) {
|
||||
this.info = info;
|
||||
public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer<AuthlibInjectorServerItem> deleteCallback) {
|
||||
this.server = server;
|
||||
|
||||
lblServerName.setStyle("-fx-font-size: 15;");
|
||||
lblServerIp.setStyle("-fx-font-size: 10;");
|
||||
@@ -54,11 +54,11 @@ public final class AuthlibInjectorServerItem extends BorderPane {
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblServerName.setText(info.getServerName());
|
||||
lblServerIp.setText(info.getServerIp());
|
||||
lblServerName.setText(server.getName());
|
||||
lblServerIp.setText(server.getUrl());
|
||||
}
|
||||
|
||||
public AuthlibInjectorServerInfo getInfo() {
|
||||
return info;
|
||||
public AuthlibInjectorServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,151 +1,69 @@
|
||||
/*
|
||||
* 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.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.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.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.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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 lblServerIp;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblServerWarning;
|
||||
@FXML private VBox listPane;
|
||||
@FXML private JFXTextField txtServerIp;
|
||||
@FXML private JFXDialogLayout addServerPane;
|
||||
@FXML private JFXDialogLayout confirmServerPane;
|
||||
@FXML private JFXDialog dialog;
|
||||
@FXML private StackPane contentPane;
|
||||
@FXML private JFXSpinner spinner;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
@FXML private JFXButton btnAddNext;
|
||||
|
||||
private final TransitionHandler transitionHandler;
|
||||
private InvalidationListener serversListener;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
public AuthlibInjectorServersPage() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
smoothScrolling(scrollPane);
|
||||
|
||||
getChildren().remove(dialog);
|
||||
dialog.setDialogContainer(this);
|
||||
|
||||
txtServerIp.textProperty().addListener((a, b, newValue) ->
|
||||
btnAddNext.setDisable(!txtServerIp.validate()));
|
||||
|
||||
loading();
|
||||
serversListener = observable -> updateServersList();
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.addListener(new WeakInvalidationListener(serversListener));
|
||||
updateServersList();
|
||||
}
|
||||
|
||||
private void removeServer(AuthlibInjectorServerItem item) {
|
||||
Settings.INSTANCE.removeAuthlibInjectorServerURL(item.getInfo().getServerIp());
|
||||
loading();
|
||||
private void updateServersList() {
|
||||
listPane.getChildren().setAll(
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.stream()
|
||||
.map(server -> new AuthlibInjectorServerItem(server,
|
||||
item -> ConfigHolder.CONFIG.authlibInjectorServers.remove(item.getServer())))
|
||||
.collect(toList()));
|
||||
}
|
||||
|
||||
private void loading() {
|
||||
getChildren().remove(contentPane);
|
||||
spinner.setVisible(true);
|
||||
|
||||
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
|
||||
.flatMap(serverURL -> {
|
||||
try {
|
||||
return Stream.of(new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer));
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Authlib-injector server root " + serverURL + " cannot be recognized.", e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList()))
|
||||
.subscribe(Task.of(Schedulers.javafx(), variables -> {
|
||||
listPane.getChildren().setAll(variables.<Collection<? extends Node>>get("list"));
|
||||
loadingCompleted();
|
||||
}));
|
||||
}
|
||||
|
||||
private void loadingCompleted() {
|
||||
getChildren().add(contentPane);
|
||||
spinner.setVisible(false);
|
||||
|
||||
if (Settings.INSTANCE.getAuthlibInjectorServerURLs().isEmpty())
|
||||
onAdd();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
|
||||
txtServerIp.setText("");
|
||||
addServerPane.setDisable(false);
|
||||
progressBar.setVisible(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddCancel() {
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddNext() {
|
||||
String serverIp = txtServerIp.getText();
|
||||
progressBar.setVisible(true);
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(serverIp))
|
||||
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
progressBar.setVisible(false);
|
||||
addServerPane.setDisable(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(variables.get("serverName"));
|
||||
lblServerIp.setText(txtServerIp.getText());
|
||||
|
||||
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverIp).getProtocol()));
|
||||
|
||||
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
|
||||
} else
|
||||
lblCreationWarning.setText(variables.<Exception>get("lastException").getLocalizedMessage());
|
||||
}).start();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddPrev() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
String ip = txtServerIp.getText();
|
||||
if (!ip.endsWith("/"))
|
||||
ip += "/";
|
||||
Settings.INSTANCE.addAuthlibInjectorServerURL(ip);
|
||||
loading();
|
||||
dialog.close();
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -28,12 +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.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -117,14 +115,9 @@ public final class Controllers {
|
||||
stage.setTitle(Launcher.TITLE);
|
||||
}
|
||||
|
||||
public static Region getDialogContent() {
|
||||
return decorator.getDialog().getContent();
|
||||
}
|
||||
|
||||
public static JFXDialog dialog(Region content) {
|
||||
// TODO: temp fix
|
||||
decorator.showDialog(Lang.apply(new Region(), region -> region.getProperties().put("controllers", true)));
|
||||
return decorator.showDialog(content);
|
||||
public static void dialog(Region content) {
|
||||
if (decorator != null)
|
||||
decorator.showDialog(content);
|
||||
}
|
||||
|
||||
public static void dialog(String text) {
|
||||
@@ -140,31 +133,37 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type, Runnable onAccept) {
|
||||
dialog(new MessageDialogPane(text, title, decorator.getDialog(), 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, decorator.getDialog(), onAccept, onCancel));
|
||||
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
|
||||
}
|
||||
|
||||
public static void inputDialog(String text, Consumer<String> onResult) {
|
||||
dialog(new InputDialogPane(text, decorator.getDialog(), onResult));
|
||||
dialog(new InputDialogPane(text, onResult));
|
||||
}
|
||||
|
||||
public static void taskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||
taskDialog(executor, title, subtitle, null);
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||
return taskDialog(executor, title, subtitle, null);
|
||||
}
|
||||
|
||||
public static void taskDialog(TaskExecutor executor, String title, String subtitle, Runnable onCancel) {
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
pane.setSubtitle(subtitle);
|
||||
pane.setExecutor(executor);
|
||||
dialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static void closeDialog() {
|
||||
decorator.getDialog().close();
|
||||
/**
|
||||
* Use {@link DialogCloseEvent}
|
||||
*/
|
||||
public static void closeDialog(Region content) {
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
decorator.closeDialog(content);
|
||||
}
|
||||
|
||||
public static void navigate(Node node) {
|
||||
@@ -175,9 +174,15 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void showUpdate() {
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -17,18 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
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;
|
||||
@@ -39,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;
|
||||
@@ -46,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;
|
||||
@@ -54,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;
|
||||
@@ -76,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);
|
||||
|
||||
@@ -84,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;
|
||||
@@ -130,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);
|
||||
@@ -167,21 +182,18 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
btnMax.fire();
|
||||
});
|
||||
|
||||
dialog = new JFXDialog() {
|
||||
@Override
|
||||
public void close() {
|
||||
dialogPane.pop();
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
Platform.runLater(() -> {
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
super.close();
|
||||
});
|
||||
}
|
||||
};
|
||||
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);
|
||||
@@ -199,17 +211,22 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
||||
|
||||
loadBackground();
|
||||
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
private void loadBackground() {
|
||||
Image background;
|
||||
try {
|
||||
Image background;
|
||||
|
||||
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
|
||||
else
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
|
||||
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
|
||||
else
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
|
||||
|
||||
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
|
||||
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private static Image searchBackgroundImage(Image def, String customPath) {
|
||||
@@ -525,6 +542,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
|
||||
public void showPage(Node content) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
||||
if (content != null)
|
||||
contentPlaceHolder.getStyleClass().add("gray-background");
|
||||
@@ -545,11 +564,40 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
}
|
||||
|
||||
public JFXDialog showDialog(Node node) {
|
||||
if (dialogPane.isEmpty())
|
||||
public void showDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialog == null) {
|
||||
dialog = new JFXDialog();
|
||||
dialogPane = new StackContainerPane();
|
||||
|
||||
dialog.setContent(dialogPane);
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setOverlayClose(false);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
dialogPane.push(node);
|
||||
return dialog;
|
||||
|
||||
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()) {
|
||||
dialog.close();
|
||||
dialog = null;
|
||||
dialogPane = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider) {
|
||||
@@ -557,6 +605,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
this.category = category;
|
||||
wizardController.setProvider(wizardProvider);
|
||||
wizardController.onStart();
|
||||
@@ -638,11 +688,13 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
return wizardController;
|
||||
}
|
||||
|
||||
public JFXDialog getDialog() {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
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))));
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public final class DialogController {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
}, latch::countDown);
|
||||
pane.setDialog(Controllers.dialog(pane));
|
||||
Controllers.dialog(pane);
|
||||
});
|
||||
latch.await();
|
||||
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.adapters.ReflectionHelper;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -31,37 +31,52 @@ import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.ListView;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.ReflectionHelper.call;
|
||||
import static org.jackhuang.hmcl.util.ReflectionHelper.construct;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public final class FXUtils {
|
||||
private FXUtils() {
|
||||
}
|
||||
|
||||
public static void checkFxUserThread() {
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
throw new IllegalStateException("Not on FX application thread; currentThread = "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static InvalidationListener onInvalidating(Runnable action) {
|
||||
return arg -> action.run();
|
||||
}
|
||||
|
||||
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener((a, b, c) -> consumer.accept(c));
|
||||
}
|
||||
@@ -118,7 +133,7 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void removeListener(Node node, String key) {
|
||||
Lang.cast(node.getProperties().get(key), ListenerPair.class)
|
||||
tryCast(node.getProperties().get(key), ListenerPair.class)
|
||||
.ifPresent(info -> {
|
||||
info.unbind();
|
||||
node.getProperties().remove(key);
|
||||
@@ -188,18 +203,12 @@ 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);
|
||||
}
|
||||
|
||||
public static void resetChildren(JFXMasonryPane pane, List<Node> children) {
|
||||
// Fixes mis-repositioning.
|
||||
ReflectionHelper.setFieldContent(JFXMasonryPane.class, pane, "oldBoxes", null);
|
||||
pane.getChildren().setAll(children);
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, String tooltip) {
|
||||
installTooltip(node, 0, 5000, 0, new Tooltip(tooltip));
|
||||
}
|
||||
@@ -207,17 +216,22 @@ public final class FXUtils {
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
try {
|
||||
// Java 8
|
||||
call(construct(Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"), new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false),
|
||||
"install", node, tooltip);
|
||||
} catch (Throwable e) {
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
try {
|
||||
// Java 9
|
||||
call(tooltip, "setShowDelay", new Duration(openDelay));
|
||||
call(tooltip, "setShowDuration", new Duration(visibleDelay));
|
||||
call(tooltip, "setHideDelay", new Duration(closeDelay));
|
||||
} catch (Throwable e2) {
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip by reflection", e);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
@@ -326,7 +340,7 @@ public final class FXUtils {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
||||
ChangeListener listener = Lang.get(comboBox.getProperties(), "FXUtils.bindEnum.listener", ChangeListener.class).orElse(null);
|
||||
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
|
||||
if (listener == null) return;
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
}
|
||||
@@ -397,7 +411,32 @@ public final class FXUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png");
|
||||
public static <T> StringConverter<T> stringConverter(Function<T, String> func) {
|
||||
return new StringConverter<T>() {
|
||||
|
||||
@Override
|
||||
public String toString(T object) {
|
||||
return object == null ? "" : func.apply(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromString(String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Callback<ListView<T>, ListCell<T>> jfxListCellFactory(Function<T, Node> graphicBuilder) {
|
||||
return view -> new JFXListCell<T>() {
|
||||
@Override
|
||||
public void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (!empty) {
|
||||
setGraphic(graphicBuilder.apply(item));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final Interpolator SINE = new Interpolator() {
|
||||
@Override
|
||||
|
||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
@@ -90,7 +91,7 @@ public class InstallerController {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
|
||||
|
||||
if (!gameVersion.isPresent())
|
||||
Controllers.dialog(Launcher.i18n("version.cannot_read"));
|
||||
Controllers.dialog(i18n("version.cannot_read"));
|
||||
else
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -44,7 +45,7 @@ public class InstallerItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblInstallerArtifact.setText(artifact);
|
||||
lblInstallerVersion.setText(Launcher.i18n("archive.version") + ": " + version);
|
||||
lblInstallerVersion.setText(i18n("archive.version") + ": " + version);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -25,9 +25,10 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
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;
|
||||
@@ -41,41 +42,47 @@ import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
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;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class LeftPaneController {
|
||||
private final AdvancedListBox leftPane;
|
||||
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 ->
|
||||
@@ -83,6 +90,7 @@ public final class LeftPaneController {
|
||||
})))
|
||||
.add(profilePane);
|
||||
|
||||
EventBus.EVENT_BUS.channel(AccountAddedEvent.class).register(this::onAccountAdd);
|
||||
EventBus.EVENT_BUS.channel(AccountLoadingEvent.class).register(this::onAccountsLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
@@ -93,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) {
|
||||
@@ -115,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") : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -136,16 +144,29 @@ 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) {
|
||||
Account account = event.getAccount();
|
||||
VersionListItem item = items.get(account);
|
||||
if (account instanceof YggdrasilAccount)
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
});
|
||||
}
|
||||
|
||||
private void onAccountsLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
items.clear();
|
||||
Account selectedAccount = Settings.INSTANCE.getSelectedAccount();
|
||||
for (Account account : Settings.INSTANCE.getAccounts()) {
|
||||
VersionListItem item = new VersionListItem(account.getCharacter(), accountType(account));
|
||||
items.put(account, item);
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
@@ -164,11 +185,11 @@ public final class LeftPaneController {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
} else
|
||||
item.setImage(AccountHelper.getDefaultSkin(account, 4), AccountHelper.getViewport(4));
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount)
|
||||
Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account)
|
||||
.subscribe(Schedulers.javafx(), variables -> FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(variables.get("serverName"))));
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
|
||||
}
|
||||
|
||||
if (selectedAccount == account)
|
||||
ripplerContainer.setSelected(true);
|
||||
@@ -187,15 +208,15 @@ public final class LeftPaneController {
|
||||
}
|
||||
|
||||
public void showUpdate() {
|
||||
launcherSettingsItem.setText(Launcher.i18n("update.found"));
|
||||
launcherSettingsItem.setText(i18n("update.found"));
|
||||
launcherSettingsItem.setTextFill(Color.RED);
|
||||
}
|
||||
|
||||
private boolean checkedModpack = false;
|
||||
private static boolean showNewAccount = true;
|
||||
|
||||
private void onRefreshedVersions(RefreshedVersionsEvent event) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
boolean flag = true;
|
||||
HMCLGameRepository repository = (HMCLGameRepository) event.getSource();
|
||||
if (!checkedModpack) {
|
||||
checkedModpack = true;
|
||||
@@ -204,22 +225,26 @@ public final class LeftPaneController {
|
||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||
if (modpackFile.exists()) {
|
||||
try {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
|
||||
Controllers.taskDialog(ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), () -> {
|
||||
Controllers.closeDialog();
|
||||
checkAccount();
|
||||
})).executor(true),
|
||||
Launcher.i18n("modpack.installing"), "", null);
|
||||
flag = false;
|
||||
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), () -> {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
checkAccount();
|
||||
})).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.installing"), ""));
|
||||
executor.start();
|
||||
showNewAccount = false;
|
||||
} catch (UnsupportedModpackException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flag)
|
||||
if (showNewAccount) {
|
||||
showNewAccount = false;
|
||||
checkAccount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,13 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
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;
|
||||
@@ -40,26 +42,26 @@ 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.construct.TaskExecutorDialogPane;
|
||||
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 org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
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;
|
||||
|
||||
@@ -73,6 +75,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
private JFXSpinner spinner;
|
||||
@FXML
|
||||
private JFXMasonryPane masonryPane;
|
||||
@FXML
|
||||
private ScrollPane scrollPane;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||
@@ -85,44 +89,48 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
JFXUtilities.runInFXAndWait(this::loadingVersions);
|
||||
// This will occupy 0.5s. Too slow!
|
||||
JFXUtilities.runInFX(this::loadingVersions);
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(event -> {
|
||||
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) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, String game) {
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, Callable<String> gameCallable) {
|
||||
Profile profile = repository.getProfile();
|
||||
String id = version.getId();
|
||||
VersionItem item = new VersionItem();
|
||||
item.setUpdate(repository.isModpack(id));
|
||||
item.setGameVersion(game);
|
||||
Task.ofResult("game", gameCallable).subscribe(Schedulers.javafx(), vars -> {
|
||||
String game = vars.get("game");
|
||||
item.setGameVersion(game);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
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(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(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
});
|
||||
item.setVersionName(id);
|
||||
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
@@ -131,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);
|
||||
@@ -151,23 +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)).executor(true);
|
||||
Controllers.taskDialog(executor, Launcher.i18n("modpack.update"), "", null);
|
||||
.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();
|
||||
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();
|
||||
Controllers.dialog(Launcher.i18n("modpack.mismatched_type"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
Controllers.closeDialog();
|
||||
Controllers.dialog(Launcher.i18n("modpack.invalid"), 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) {
|
||||
region.get().fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -178,12 +188,12 @@ 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 ->{
|
||||
versionList.setOnMouseClicked(e -> {
|
||||
versionPopup.hide();
|
||||
switch (versionList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0:
|
||||
@@ -200,11 +210,12 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}});
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
@@ -217,18 +228,17 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private void loadingVersions() {
|
||||
getChildren().setAll(spinner);
|
||||
FXUtils.resetChildren(masonryPane, Collections.emptyList());
|
||||
masonryPane.getChildren().clear();
|
||||
}
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = new LinkedList<>();
|
||||
for (Version version : repository.getVersions()) {
|
||||
children.add(buildNode(repository, version, GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")));
|
||||
}
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == repository.getProfile()) {
|
||||
masonryPane.getChildren().setAll(children);
|
||||
getChildren().setAll(contentPane);
|
||||
FXUtils.resetChildren(masonryPane, children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,21 +26,23 @@ 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;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class ModController {
|
||||
@FXML
|
||||
@@ -68,21 +70,22 @@ public final class ModController {
|
||||
});
|
||||
|
||||
rootPane.setOnDragDropped(event -> {
|
||||
List<File> mods = event.getDragboard().getFiles();
|
||||
Stream<File> stream = null;
|
||||
if (mods != null)
|
||||
stream = mods.stream()
|
||||
.filter(it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)));
|
||||
if (stream != null && stream.findAny().isPresent()) {
|
||||
stream.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(versionId, it);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
||||
}
|
||||
});
|
||||
loadMods(modManager, versionId);
|
||||
event.setDropCompleted(true);
|
||||
List<File> files = event.getDragboard().getFiles();
|
||||
if (files != null) {
|
||||
Collection<File> mods = files.stream()
|
||||
.filter(it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)))
|
||||
.collect(Collectors.toList());
|
||||
if (!mods.isEmpty()) {
|
||||
mods.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(versionId, it);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
||||
}
|
||||
});
|
||||
loadMods(modManager, versionId);
|
||||
event.setDropCompleted(true);
|
||||
}
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
@@ -137,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.
|
||||
@@ -160,10 +163,10 @@ public final class ModController {
|
||||
}).with(Task.of(Schedulers.javafx(), variables -> {
|
||||
List<String> prompt = new LinkedList<>();
|
||||
if (!succeeded.isEmpty())
|
||||
prompt.add(Launcher.i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
|
||||
if (!failed.isEmpty())
|
||||
prompt.add(Launcher.i18n("mods.add.failed", String.join(", ", failed)));
|
||||
Controllers.dialog(String.join("\n", prompt), Launcher.i18n("mods.add"));
|
||||
prompt.add(i18n("mods.add.failed", String.join(", ", failed)));
|
||||
Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
|
||||
loadMods(modManager, versionId);
|
||||
})).start();
|
||||
}
|
||||
|
||||
@@ -17,14 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXCheckBox;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -40,7 +42,9 @@ public final class ModItem extends BorderPane {
|
||||
setCenter(modItem);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
FXUtils.installTooltip(btnRemove, Launcher.i18n("mods.remove"));
|
||||
JFXUtilities.runInFX(() -> {
|
||||
FXUtils.installTooltip(btnRemove, i18n("mods.remove"));
|
||||
});
|
||||
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
@@ -50,7 +54,7 @@ public final class ModItem extends BorderPane {
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
modItem.setTitle(info.getFileName());
|
||||
modItem.setSubtitle(info.getName() + ", " + Launcher.i18n("archive.version") + ": " + info.getVersion() + ", " + Launcher.i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + Launcher.i18n("archive.author") + ": " + info.getAuthors());
|
||||
modItem.setSubtitle(info.getName() + ", " + i18n("archive.version") + ": " + info.getVersion() + ", " + i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + i18n("archive.author") + ": " + info.getAuthors());
|
||||
chkEnabled.setSelected(info.isActive());
|
||||
chkEnabled.selectedProperty().addListener((a, b, newValue) ->
|
||||
info.activeProperty().set(newValue));
|
||||
|
||||
@@ -23,13 +23,14 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Optional;
|
||||
@@ -54,7 +55,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
|
||||
|
||||
title = new SimpleStringProperty(this, "title",
|
||||
profile == null ? Launcher.i18n("profile.new") : Launcher.i18n("profile") + " - " + profileDisplayName);
|
||||
profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName);
|
||||
location = new SimpleStringProperty(this, "location",
|
||||
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@ import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
@@ -49,7 +52,7 @@ import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("settings.launcher"));
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("settings.launcher"));
|
||||
|
||||
@FXML
|
||||
private JFXTextField txtProxyHost;
|
||||
@@ -177,12 +180,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
|
||||
|
||||
FXUtils.installTooltip(btnUpdate, Launcher.i18n("update.tooltip"));
|
||||
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
checkUpdate();
|
||||
|
||||
// background
|
||||
backgroundItem.loadChildren(Collections.singletonList(
|
||||
backgroundItem.createChildren(Launcher.i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
));
|
||||
|
||||
FXUtils.bindString(backgroundItem.getTxtCustom(), Settings.INSTANCE.backgroundImageProperty());
|
||||
@@ -199,8 +202,8 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
// theme
|
||||
JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null);
|
||||
picker.setCustomColorText(Launcher.i18n("color.custom"));
|
||||
picker.setRecentColorsText(Launcher.i18n("color.recent"));
|
||||
picker.setCustomColorText(i18n("color.custom"));
|
||||
picker.setRecentColorsText(i18n("color.recent"));
|
||||
picker.getCustomColors().setAll(Arrays.stream(Theme.VALUES).map(Theme::getColor).map(Color::web).collect(Collectors.toList()));
|
||||
picker.setOnAction(e -> {
|
||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
||||
@@ -214,7 +217,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private void initBackgroundItemSubtitle() {
|
||||
switch (Settings.INSTANCE.getBackgroundImageType()) {
|
||||
case DEFAULT:
|
||||
backgroundItem.setSubtitle(Launcher.i18n("launcher.background.default"));
|
||||
backgroundItem.setSubtitle(i18n("launcher.background.default"));
|
||||
break;
|
||||
case CUSTOM:
|
||||
backgroundItem.setSubtitle(Settings.INSTANCE.getBackgroundImage());
|
||||
@@ -239,16 +242,16 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
btnUpdate.setVisible(Launcher.UPDATE_CHECKER.isOutOfDate());
|
||||
|
||||
if (Launcher.UPDATE_CHECKER.isOutOfDate()) {
|
||||
lblUpdateSub.setText(Launcher.i18n("update.newest_version", Launcher.UPDATE_CHECKER.getNewVersion().toString()));
|
||||
lblUpdateSub.setText(i18n("update.newest_version", Launcher.UPDATE_CHECKER.getNewVersion().toString()));
|
||||
lblUpdateSub.getStyleClass().setAll("update-label");
|
||||
|
||||
lblUpdate.setText(Launcher.i18n("update.found"));
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
} else {
|
||||
lblUpdateSub.setText(Launcher.i18n("update.latest"));
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(Launcher.i18n("update"));
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.ui;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.effect.BlurType;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
@@ -28,12 +31,12 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -65,18 +68,119 @@ public final class VersionItem extends StackPane {
|
||||
|
||||
private EventHandler<? super MouseEvent> launchClickedHandler = null;
|
||||
|
||||
private void initializeComponents() {
|
||||
setPickOnBounds(false);
|
||||
FXUtils.setLimitWidth(this, 160);
|
||||
FXUtils.setLimitHeight(this, 156);
|
||||
|
||||
content = new VBox();
|
||||
{
|
||||
header = new StackPane();
|
||||
VBox.setVgrow(header, Priority.ALWAYS);
|
||||
header.setPickOnBounds(false);
|
||||
header.setPadding(new Insets(8));
|
||||
header.setStyle("-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87);");
|
||||
{
|
||||
VBox headerContent = new VBox();
|
||||
headerContent.setPadding(new Insets(8, 8, 0, 8));
|
||||
{
|
||||
lblVersionName = new Label();
|
||||
lblVersionName.setStyle("-fx-font-size: 15;");
|
||||
lblVersionName.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblVersionName.setWrapText(true);
|
||||
headerContent.getChildren().add(lblVersionName);
|
||||
|
||||
lblGameVersion = new Label();
|
||||
lblGameVersion.setStyle("-fx-font-size: 11;");
|
||||
lblGameVersion.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblGameVersion.setWrapText(true);
|
||||
headerContent.getChildren().add(lblGameVersion);
|
||||
|
||||
lblLibraries = new Label();
|
||||
lblLibraries.setStyle("-fx-font-size: 10; -fx-text-fill: gray;");
|
||||
lblLibraries.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblLibraries.setWrapText(true);
|
||||
headerContent.getChildren().add(lblLibraries);
|
||||
}
|
||||
header.getChildren().add(headerContent);
|
||||
}
|
||||
content.getChildren().add(header);
|
||||
|
||||
body = new StackPane();
|
||||
body.setStyle("-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;");
|
||||
body.setMinHeight(40);
|
||||
body.setPickOnBounds(false);
|
||||
{
|
||||
BorderPane bodyContent = new BorderPane();
|
||||
{
|
||||
HBox hbox = new HBox();
|
||||
hbox.setSpacing(8);
|
||||
{
|
||||
btnSettings = new JFXButton();
|
||||
btnSettings.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnSettings, 30);
|
||||
FXUtils.setLimitHeight(btnSettings, 30);
|
||||
hbox.getChildren().add(btnSettings);
|
||||
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnUpdate, 30);
|
||||
FXUtils.setLimitHeight(btnUpdate, 30);
|
||||
hbox.getChildren().add(btnUpdate);
|
||||
}
|
||||
bodyContent.setLeft(hbox);
|
||||
}
|
||||
{
|
||||
HBox hbox = new HBox();
|
||||
hbox.setSpacing(8);
|
||||
{
|
||||
btnScript = new JFXButton();
|
||||
btnScript.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnScript, 30);
|
||||
FXUtils.setLimitHeight(btnScript, 30);
|
||||
hbox.getChildren().add(btnScript);
|
||||
|
||||
btnLaunch = new JFXButton();
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnLaunch, 30);
|
||||
FXUtils.setLimitHeight(btnLaunch, 30);
|
||||
hbox.getChildren().add(btnLaunch);
|
||||
}
|
||||
bodyContent.setRight(hbox);
|
||||
}
|
||||
body.getChildren().setAll(bodyContent);
|
||||
}
|
||||
content.getChildren().add(body);
|
||||
}
|
||||
getChildren().add(content);
|
||||
|
||||
icon = new StackPane();
|
||||
StackPane.setAlignment(icon, Pos.TOP_RIGHT);
|
||||
icon.setPickOnBounds(false);
|
||||
{
|
||||
iconView = new ImageView();
|
||||
StackPane.setAlignment(iconView, Pos.CENTER_RIGHT);
|
||||
StackPane.setMargin(iconView, new Insets(0, 12, 0, 0));
|
||||
iconView.setImage(new Image("/assets/img/icon.png"));
|
||||
icon.getChildren().add(iconView);
|
||||
}
|
||||
getChildren().add(icon);
|
||||
}
|
||||
|
||||
public VersionItem() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version-item.fxml");
|
||||
initializeComponents();
|
||||
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
|
||||
btnSettings.setGraphic(SVG.gear(Theme.blackFillBinding(), 15, 15));
|
||||
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 15, 15));
|
||||
btnLaunch.setGraphic(SVG.launch(Theme.blackFillBinding(), 15, 15));
|
||||
btnScript.setGraphic(SVG.script(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
FXUtils.installTooltip(btnSettings, Launcher.i18n("version.settings"));
|
||||
FXUtils.installTooltip(btnUpdate, Launcher.i18n("version.update"));
|
||||
FXUtils.installTooltip(btnLaunch, Launcher.i18n("version.launch"));
|
||||
FXUtils.installTooltip(btnScript, Launcher.i18n("version.launch_script"));
|
||||
JFXUtilities.runInFX(() -> {
|
||||
FXUtils.installTooltip(btnSettings, i18n("version.settings"));
|
||||
FXUtils.installTooltip(btnUpdate, i18n("version.update"));
|
||||
FXUtils.installTooltip(btnLaunch, i18n("version.launch"));
|
||||
FXUtils.installTooltip(btnScript, i18n("version.launch_script"));
|
||||
});
|
||||
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
FXUtils.limitSize(iconView, 32, 32);
|
||||
|
||||
@@ -26,12 +26,13 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -79,17 +80,17 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
browsePopup = new JFXPopup(browseList);
|
||||
managementPopup = new JFXPopup(managementList);
|
||||
|
||||
FXUtils.installTooltip(btnDelete, Launcher.i18n("version.manage.remove"));
|
||||
FXUtils.installTooltip(btnBrowseMenu, Launcher.i18n("settings.game.exploration"));
|
||||
FXUtils.installTooltip(btnManagementMenu, Launcher.i18n("settings.game.management"));
|
||||
FXUtils.installTooltip(btnExport, Launcher.i18n("modpack.export"));
|
||||
FXUtils.installTooltip(btnDelete, i18n("version.manage.remove"));
|
||||
FXUtils.installTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
|
||||
FXUtils.installTooltip(btnManagementMenu, i18n("settings.game.management"));
|
||||
FXUtils.installTooltip(btnExport, i18n("modpack.export"));
|
||||
}
|
||||
|
||||
public void load(String id, Profile profile) {
|
||||
this.version = id;
|
||||
this.profile = profile;
|
||||
|
||||
title.set(Launcher.i18n("settings.game") + " - " + id);
|
||||
title.set(i18n("settings.game") + " - " + id);
|
||||
|
||||
versionSettingsController.loadVersionSetting(profile, id);
|
||||
modController.setParentTab(tabPane);
|
||||
@@ -185,7 +186,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
Controllers.confirmDialog(Launcher.i18n("version.manage.remove.confirm", version), Launcher.i18n("message.confirm"), () -> {
|
||||
Controllers.confirmDialog(i18n("version.manage.remove.confirm", version), i18n("message.confirm"), () -> {
|
||||
if (profile.getRepository().removeVersionFromDisk(version)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
@@ -194,7 +195,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void renameVersion(Profile profile, String version) {
|
||||
Controllers.inputDialog(Launcher.i18n("version.manage.rename.message"), res -> {
|
||||
Controllers.inputDialog(i18n("version.manage.rename.message"), res -> {
|
||||
if (profile.getRepository().renameVersion(version, res)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
@@ -203,6 +204,6 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
|
||||
public static void exportVersion(Profile profile, String version) {
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Launcher.i18n("modpack.wizard"));
|
||||
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), i18n("modpack.wizard"));
|
||||
}
|
||||
}
|
||||
@@ -23,14 +23,13 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.controls.JFXToggleButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
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;
|
||||
@@ -40,11 +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.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -80,33 +80,34 @@ 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);
|
||||
|
||||
Task.of(variables -> variables.set("list", JavaVersion.getJREs().values().stream().map(javaVersion ->
|
||||
javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion)
|
||||
).collect(Collectors.toList()))).subscribe(Schedulers.javafx(), variables ->
|
||||
javaItem.loadChildren(variables.<Collection<Node>>get("list"))
|
||||
);
|
||||
Task.of(variables -> variables.set("list", JavaVersion.getJREs()))
|
||||
.subscribe(Schedulers.javafx(), variables -> {
|
||||
javaItem.loadChildren(
|
||||
(variables.<List<JavaVersion>>get("list")).stream()
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion))
|
||||
.collect(Collectors.toList()));
|
||||
initializeSelectedJava();
|
||||
});
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
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;
|
||||
|
||||
@@ -115,6 +116,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());
|
||||
@@ -134,6 +136,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());
|
||||
@@ -151,29 +159,17 @@ public final class VersionSettingsController {
|
||||
FXUtils.bindBoolean(chkShowLogs, versionSetting.showLogsProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
|
||||
boolean flag = false;
|
||||
Toggle defaultToggle = null;
|
||||
for (Toggle toggle : javaItem.getGroup().getToggles()) {
|
||||
if (toggle.getUserData() == Lang.invoke(versionSetting::getJavaVersion)) {
|
||||
toggle.setSelected(true);
|
||||
flag = true;
|
||||
} else if (toggle.getUserData() == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = toggle;
|
||||
}
|
||||
}
|
||||
|
||||
javaItem.setToggleSelectedListener(newValue -> {
|
||||
if (javaItem.isCustomToggle(newValue)) {
|
||||
versionSetting.setJava("Custom");
|
||||
versionSetting.setDefaultJavaPath(null);
|
||||
} else {
|
||||
versionSetting.setJava(((JavaVersion) newValue.getUserData()).getVersion());
|
||||
JavaVersion java = (JavaVersion) newValue.getUserData();
|
||||
versionSetting.setJava(java.getVersion());
|
||||
versionSetting.setDefaultJavaPath(java.getBinary().toString());
|
||||
}
|
||||
});
|
||||
|
||||
if (!flag) {
|
||||
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
|
||||
}
|
||||
|
||||
versionSetting.javaDirProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
initJavaSubtitle(versionSetting);
|
||||
@@ -183,6 +179,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
|
||||
@@ -192,7 +190,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())
|
||||
@@ -207,9 +205,33 @@ public final class VersionSettingsController {
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
initializeSelectedJava();
|
||||
|
||||
loadIcon();
|
||||
}
|
||||
|
||||
private void initializeSelectedJava() {
|
||||
if (lastVersionSetting == null
|
||||
|| !JavaVersion.getJREsImmediately().isPresent() /* 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
|
||||
}
|
||||
}
|
||||
|
||||
private void initJavaSubtitle(VersionSetting versionSetting) {
|
||||
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
|
||||
.subscribe(Task.of(Schedulers.javafx(),
|
||||
@@ -221,18 +243,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);
|
||||
@@ -250,7 +264,7 @@ public final class VersionSettingsController {
|
||||
if (iconFile.exists())
|
||||
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
|
||||
else
|
||||
iconPickerItem.setImage(FXUtils.DEFAULT_ICON);
|
||||
iconPickerItem.setImage(Constants.DEFAULT_ICON.get());
|
||||
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
|
||||
}
|
||||
}
|
||||
@@ -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,9 +19,11 @@ 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;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public final class TransitionHandler implements AnimationHandler {
|
||||
@@ -36,6 +38,12 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
public TransitionHandler(StackPane view) {
|
||||
this.view = view;
|
||||
currentNode = view.getChildren().stream().findFirst().orElse(null);
|
||||
|
||||
// prevent content overflow
|
||||
Rectangle clip = new Rectangle();
|
||||
clip.widthProperty().bind(view.widthProperty());
|
||||
clip.heightProperty().bind(view.heightProperty());
|
||||
view.setClip(clip);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,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 == currentNode)
|
||||
previousNode = NULL;
|
||||
if (previousNode == newView)
|
||||
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();
|
||||
}
|
||||
+21
-12
@@ -15,22 +15,31 @@
|
||||
* 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;
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
public class AuthlibInjectorServerInfo {
|
||||
private final String serverIp;
|
||||
private final String serverName;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
public AuthlibInjectorServerInfo(String serverIp, String serverName) {
|
||||
this.serverIp = serverIp;
|
||||
this.serverName = serverName;
|
||||
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 String getServerIp() {
|
||||
return serverIp;
|
||||
public DialogCloseEvent(Object source, EventTarget target) {
|
||||
super(source, target, CLOSE);
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,12 @@ import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -54,7 +55,7 @@ public class FileItem extends BorderPane {
|
||||
right.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
right.setOnMouseClicked(e -> onExplore());
|
||||
FXUtils.installTooltip(right, Launcher.i18n("button.edit"));
|
||||
FXUtils.installTooltip(right, i18n("button.edit"));
|
||||
setRight(right);
|
||||
|
||||
Tooltip tip = new Tooltip();
|
||||
|
||||
@@ -15,7 +15,9 @@ import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
@@ -41,7 +43,7 @@ public final class ImagePickerItem extends BorderPane {
|
||||
selectButton.onMouseClickedProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
FXUtils.installTooltip(selectButton, Launcher.i18n("button.edit"));
|
||||
FXUtils.installTooltip(selectButton, i18n("button.edit"));
|
||||
|
||||
HBox hBox = new HBox();
|
||||
hBox.getChildren().setAll(imageView, selectButton);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -28,8 +27,6 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class InputDialogPane extends StackPane {
|
||||
private final String text;
|
||||
private final JFXDialog dialog;
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@@ -40,16 +37,13 @@ public class InputDialogPane extends StackPane {
|
||||
@FXML
|
||||
private Label content;
|
||||
|
||||
public InputDialogPane(String text, JFXDialog dialog, Consumer<String> onResult) {
|
||||
this.text = text;
|
||||
this.dialog = dialog;
|
||||
|
||||
public InputDialogPane(String text, Consumer<String> onResult) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
||||
content.setText(text);
|
||||
cancelButton.setOnMouseClicked(e -> dialog.close());
|
||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
onResult.accept(textField.getText());
|
||||
dialog.close();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,21 +17,22 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
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
|
||||
|
||||
@@ -18,20 +18,19 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
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;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
private boolean closingDialog = true;
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@@ -46,7 +45,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
@FXML
|
||||
private HBox actions;
|
||||
|
||||
public MessageDialogPane(String text, String title, JFXDialog dialog, 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)
|
||||
@@ -54,8 +53,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
|
||||
content.setText(text);
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
if (closingDialog)
|
||||
dialog.close();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
|
||||
});
|
||||
|
||||
@@ -82,23 +80,18 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
public MessageDialogPane(String text, String title, JFXDialog dialog, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, dialog, 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 -> {
|
||||
if (closingDialog)
|
||||
dialog.close();
|
||||
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();
|
||||
|
||||
@@ -32,27 +32,26 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
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 SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||
private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
|
||||
|
||||
private final ToggleGroup group = new ToggleGroup();
|
||||
@@ -112,10 +111,6 @@ public class MultiFileItem extends ComponentList {
|
||||
if (toggleSelectedListener != null)
|
||||
toggleSelectedListener.accept(newValue);
|
||||
});
|
||||
|
||||
Tooltip tip = new Tooltip();
|
||||
tip.textProperty().bind(tooltipProperty());
|
||||
Tooltip.install(this, tip);
|
||||
}
|
||||
|
||||
public Node createChildren(String title) {
|
||||
@@ -153,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());
|
||||
@@ -230,16 +225,4 @@ public class MultiFileItem extends ComponentList {
|
||||
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {
|
||||
return extensionFilters;
|
||||
}
|
||||
|
||||
public String getTooltip() {
|
||||
return tooltip.get();
|
||||
}
|
||||
|
||||
public StringProperty tooltipProperty() {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
public void setTooltip(String tooltip) {
|
||||
this.tooltip.set(tooltip);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXSpinner;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public class SpinnerPane extends StackPane {
|
||||
private final TransitionHandler transitionHandler = new TransitionHandler(this);
|
||||
private final JFXSpinner spinner = new JFXSpinner();
|
||||
private final StackPane contentPane = new StackPane();
|
||||
private final ObjectProperty<Node> content = new SimpleObjectProperty<>(this, "content");
|
||||
|
||||
public SpinnerPane() {
|
||||
getChildren().setAll(contentPane);
|
||||
|
||||
content.addListener((a, b, newValue) -> contentPane.getChildren().setAll(newValue));
|
||||
}
|
||||
|
||||
public void showSpinner() {
|
||||
transitionHandler.setContent(spinner, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
public void hideSpinner() {
|
||||
transitionHandler.setContent(contentPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
public Node getContent() {
|
||||
return content.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> contentProperty() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void setContent(Node content) {
|
||||
this.content.set(content);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
@@ -26,17 +27,20 @@ public class StackContainerPane extends StackPane {
|
||||
private final Stack<Node> stack = new Stack<>();
|
||||
|
||||
public void push(Node node) {
|
||||
if (!node.getProperties().containsKey("controllers"))
|
||||
stack.push(node);
|
||||
stack.push(node);
|
||||
getChildren().setAll(node);
|
||||
|
||||
Logging.LOG.info(this + " " + stack);
|
||||
}
|
||||
|
||||
public void pop() {
|
||||
stack.pop();
|
||||
public void pop(Node node) {
|
||||
boolean flag = stack.remove(node);
|
||||
if (stack.isEmpty())
|
||||
getChildren().setAll();
|
||||
else
|
||||
getChildren().setAll(stack.peek());
|
||||
|
||||
Logging.LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
|
||||
@@ -23,15 +23,17 @@ import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TaskExecutorDialogPane extends StackPane {
|
||||
private TaskExecutor executor;
|
||||
private Runnable onCancel;
|
||||
private Consumer<Region> onCancel;
|
||||
|
||||
@FXML
|
||||
private JFXProgressBar progressBar;
|
||||
@@ -44,14 +46,14 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
@FXML
|
||||
private TaskListPane taskListPane;
|
||||
|
||||
public TaskExecutorDialogPane(Runnable cancel) {
|
||||
public TaskExecutorDialogPane(Consumer<Region> cancel) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/task-dialog.fxml");
|
||||
|
||||
setCancel(cancel);
|
||||
|
||||
btnCancel.setOnMouseClicked(e -> {
|
||||
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
|
||||
onCancel.run();
|
||||
onCancel.accept(this);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,7 +95,7 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
progressBar.setProgress(progress);
|
||||
}
|
||||
|
||||
public void setCancel(Runnable onCancel) {
|
||||
public void setCancel(Consumer<Region> onCancel) {
|
||||
this.onCancel = onCancel;
|
||||
|
||||
JFXUtilities.runInFX(() -> btnCancel.setDisable(onCancel == null));
|
||||
|
||||
+8
-7
@@ -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;
|
||||
|
||||
@@ -33,12 +34,12 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(() -> {
|
||||
Controllers.closeDialog();
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(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.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);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
@@ -17,10 +16,6 @@ public class URLValidator extends ValidatorBase {
|
||||
super();
|
||||
}
|
||||
|
||||
public URLValidator(@NamedArg("message") String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ObservableList<String> getProtocols() {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
@@ -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,11 +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;
|
||||
@@ -67,9 +67,9 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
File selected = Lang.get(settings, ModpackPage.MODPACK_FILE, File.class).orElse(null);
|
||||
Modpack modpack = Lang.get(settings, ModpackPage.MODPACK_CURSEFORGE_MANIFEST, Modpack.class).orElse(null);
|
||||
String name = Lang.get(settings, ModpackPage.MODPACK_NAME, String.class).orElse(null);
|
||||
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack);
|
||||
@@ -77,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);
|
||||
@@ -101,10 +101,10 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
case 1:
|
||||
return new ModpackPage(controller);
|
||||
default:
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + subStep + ", settings: " + settings);
|
||||
throw new IllegalStateException("Error step " + step + ", subStep " + subStep + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
default:
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings);
|
||||
throw new IllegalStateException("error step " + step + ", settings: " + settings + ", pages: " + controller.getPages());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,10 +20,11 @@ package org.jackhuang.hmcl.ui.download;
|
||||
import com.jfoenix.controls.JFXListView;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -49,7 +50,7 @@ public final class InstallTypePage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("install.select");
|
||||
return i18n("install.select");
|
||||
}
|
||||
|
||||
public static final String INSTALL_TYPE = "INSTALL_TYPE";
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -80,8 +81,8 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
settings.put("success_message", Launcher.i18n("install.success"));
|
||||
settings.put("failure_message", Launcher.i18n("install.failed"));
|
||||
settings.put("success_message", i18n("install.success"));
|
||||
settings.put("failure_message", i18n("install.failed"));
|
||||
|
||||
Task ret = Task.empty();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -75,30 +76,30 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
String gameVersion = ((RemoteVersion<?>) controller.getSettings().get("game")).getGameVersion();
|
||||
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
|
||||
hasVersion.setMessage(Launcher.i18n("install.new_game.already_exists"));
|
||||
hasVersion.setMessage(i18n("install.new_game.already_exists"));
|
||||
txtName.getValidators().add(hasVersion);
|
||||
txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate()));
|
||||
txtName.setText(gameVersion);
|
||||
|
||||
btnForge.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 0);
|
||||
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
|
||||
});
|
||||
|
||||
btnLiteLoader.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 1);
|
||||
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
|
||||
});
|
||||
|
||||
btnOptiFine.setOnMouseClicked(e -> {
|
||||
controller.getSettings().put(INSTALLER_TYPE, 2);
|
||||
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
|
||||
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("install.new_game");
|
||||
return i18n("install.new_game");
|
||||
}
|
||||
|
||||
private String getVersion(String id) {
|
||||
@@ -107,21 +108,21 @@ public class InstallersPage extends StackPane implements WizardPage {
|
||||
|
||||
@Override
|
||||
public void onNavigate(Map<String, Object> settings) {
|
||||
lblGameVersion.setText(Launcher.i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
|
||||
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
|
||||
if (controller.getSettings().containsKey("forge"))
|
||||
lblForge.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.forge")) + ": " + getVersion("forge"));
|
||||
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + getVersion("forge"));
|
||||
else
|
||||
lblForge.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.forge")));
|
||||
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
|
||||
|
||||
if (controller.getSettings().containsKey("liteloader"))
|
||||
lblLiteLoader.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
|
||||
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
|
||||
else
|
||||
lblLiteLoader.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.liteloader")));
|
||||
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
|
||||
|
||||
if (controller.getSettings().containsKey("optifine"))
|
||||
lblOptiFine.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
|
||||
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
|
||||
else
|
||||
lblOptiFine.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.optifine")));
|
||||
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,7 +25,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
@@ -75,28 +76,28 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
Profile profile = (Profile) controller.getSettings().get("PROFILE");
|
||||
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(Launcher.i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
|
||||
chooser.setTitle(i18n("modpack.choose"));
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile == null) Platform.runLater(() -> Controllers.navigate(null));
|
||||
else {
|
||||
controller.getSettings().put(MODPACK_FILE, selectedFile);
|
||||
lblModpackLocation.setText(selectedFile.getAbsolutePath());
|
||||
txtModpackName.getValidators().addAll(
|
||||
new Validator(Launcher.i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
|
||||
new Validator(Launcher.i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
|
||||
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
|
||||
new Validator(i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
|
||||
);
|
||||
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
|
||||
|
||||
try {
|
||||
manifest = ModpackHelper.readModpackManifest(selectedFile);
|
||||
controller.getSettings().put(MODPACK_CURSEFORGE_MANIFEST, manifest);
|
||||
controller.getSettings().put(MODPACK_MANIFEST, manifest);
|
||||
lblName.setText(manifest.getName());
|
||||
lblVersion.setText(manifest.getVersion());
|
||||
lblAuthor.setText(manifest.getAuthor());
|
||||
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
|
||||
} catch (UnsupportedModpackException e) {
|
||||
txtModpackName.setText(Launcher.i18n("modpack.task.install.error"));
|
||||
txtModpackName.setText(i18n("modpack.task.install.error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,17 +119,17 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
if (manifest != null) {
|
||||
WebStage stage = new WebStage();
|
||||
stage.getWebView().getEngine().loadContent(manifest.getDescription());
|
||||
stage.setTitle(Launcher.i18n("modpack.wizard.step.3"));
|
||||
stage.setTitle(i18n("modpack.wizard.step.3"));
|
||||
stage.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return Launcher.i18n("modpack.task.install");
|
||||
return i18n("modpack.task.install");
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE = "MODPACK_FILE";
|
||||
public static final String MODPACK_NAME = "MODPACK_NAME";
|
||||
public static final String MODPACK_CURSEFORGE_MANIFEST = "CURSEFORGE_MANIFEST";
|
||||
public static final String MODPACK_MANIFEST = "MODPACK_MANIFEST";
|
||||
}
|
||||
@@ -23,12 +23,13 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.download.game.GameRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersionTag;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -57,15 +58,15 @@ public final class VersionsPageItem extends StackPane {
|
||||
if (remoteVersion.getTag() instanceof GameRemoteVersionTag) {
|
||||
switch (((GameRemoteVersionTag) remoteVersion.getTag()).getType()) {
|
||||
case RELEASE:
|
||||
lblGameVersion.setText(Launcher.i18n("version.game.release"));
|
||||
lblGameVersion.setText(i18n("version.game.release"));
|
||||
imageView.setImage(new Image("/assets/img/icon.png", 32, 32, false, true));
|
||||
break;
|
||||
case SNAPSHOT:
|
||||
lblGameVersion.setText(Launcher.i18n("version.game.snapshot"));
|
||||
lblGameVersion.setText(i18n("version.game.snapshot"));
|
||||
imageView.setImage(new Image("/assets/img/command.png", 32, 32, false, true));
|
||||
break;
|
||||
default:
|
||||
lblGameVersion.setText(Launcher.i18n("version.game.old"));
|
||||
lblGameVersion.setText(i18n("version.game.old"));
|
||||
imageView.setImage(new Image("/assets/img/grass.png", 32, 32, false, true));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -18,14 +18,23 @@
|
||||
package org.jackhuang.hmcl.ui.export;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
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;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.ZipEngine;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -46,16 +55,72 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> whitelist = (List<String>) settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION);
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
boolean includeLauncher = (Boolean) settings.get(ModpackInfoPage.MODPACK_INCLUDE_LAUNCHER) && launcherJar != null;
|
||||
|
||||
return new HMCLModpackExportTask(profile.getRepository(), version, whitelist,
|
||||
new Modpack(
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
|
||||
null,
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
|
||||
null
|
||||
), (File) settings.get(ModpackInfoPage.MODPACK_FILE));
|
||||
return new Task() {
|
||||
Task dependency = null;
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
File modpackFile = (File) settings.get(ModpackInfoPage.MODPACK_FILE);
|
||||
File tempModpack = includeLauncher ? Files.createTempFile("hmcl", ".zip").toFile() : modpackFile;
|
||||
|
||||
dependency = new HMCLModpackExportTask(profile.getRepository(), version, whitelist,
|
||||
new Modpack(
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
|
||||
null,
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
|
||||
null
|
||||
), tempModpack);
|
||||
|
||||
if (includeLauncher) {
|
||||
dependency = dependency.then(Task.of(() -> {
|
||||
boolean flag = true;
|
||||
|
||||
try (ZipEngine zip = new ZipEngine(modpackFile)) {
|
||||
Config config = Settings.INSTANCE.getRawConfig();
|
||||
|
||||
config.hasProxy.set(false);
|
||||
config.selectedProfile.set("");
|
||||
config.commonDirectory.set(null);
|
||||
config.fontFamily.set("Consolas");
|
||||
config.fontSize.set(12);
|
||||
config.localization.set(null);
|
||||
config.accounts.clear();
|
||||
config.selectedAccount.set("");
|
||||
config.logLines.set(100);
|
||||
config.configurations.clear();
|
||||
|
||||
zip.putTextFile(config.toJson(), ConfigHolder.CONFIG_FILENAME);
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
|
||||
File bg = new File("bg").getAbsoluteFile();
|
||||
if (bg.isDirectory())
|
||||
zip.putDirectory(bg);
|
||||
|
||||
File background_png = new File("background.png").getAbsoluteFile();
|
||||
if (background_png.isFile())
|
||||
zip.putFile(background_png, "background.png");
|
||||
|
||||
File background_jpg = new File("background.jpg").getAbsoluteFile();
|
||||
if (background_jpg.isFile())
|
||||
zip.putFile(background_jpg, "background.jpg");
|
||||
|
||||
for (File jar : launcherJar)
|
||||
zip.putFile(jar, jar.getName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return Collections.singleton(dependency);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
@@ -33,16 +33,17 @@ import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
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.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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
|
||||
*/
|
||||
@@ -150,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 = Lang.mapOf(
|
||||
new Pair<>("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")),
|
||||
new Pair<>("minecraft/saves", Launcher.i18n("modpack.files.saves")),
|
||||
new Pair<>("minecraft/mods", Launcher.i18n("modpack.files.mods")),
|
||||
new Pair<>("minecraft/config", Launcher.i18n("modpack.files.config")),
|
||||
new Pair<>("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")),
|
||||
new Pair<>("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")),
|
||||
new Pair<>("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")),
|
||||
new Pair<>("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")),
|
||||
new Pair<>("minecraft/dumps", Launcher.i18n("modpack.files.dumps")),
|
||||
new Pair<>("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")),
|
||||
new Pair<>("minecraft/scripts", Launcher.i18n("modpack.files.scripts"))
|
||||
private static final Map<String, String> TRANSLATION = mapOf(
|
||||
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,8 +33,10 @@ 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;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -45,12 +47,16 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
@FXML
|
||||
private JFXTextField txtModpackName;
|
||||
@FXML
|
||||
private JFXTextField txtModpackAuthor;@FXML
|
||||
private JFXTextField txtModpackVersion;@FXML
|
||||
private JFXTextField txtModpackAuthor;
|
||||
@FXML
|
||||
private JFXTextField txtModpackVersion;
|
||||
@FXML
|
||||
private JFXTextArea txtModpackDescription;
|
||||
@FXML
|
||||
private JFXToggleButton chkIncludeLauncher;@FXML
|
||||
private JFXButton btnNext;@FXML
|
||||
private JFXToggleButton chkIncludeLauncher;
|
||||
@FXML
|
||||
private JFXButton btnNext;
|
||||
@FXML
|
||||
private ScrollPane scroll;
|
||||
|
||||
public ModpackInfoPage(WizardController controller, String version) {
|
||||
@@ -63,6 +69,10 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
txtModpackVersion.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackAuthor.setText(Optional.ofNullable(Settings.INSTANCE.getSelectedAccount()).map(Account::getUsername).orElse(""));
|
||||
lblVersionName.setText(version);
|
||||
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
if (launcherJar == null)
|
||||
chkIncludeLauncher.setDisable(true);
|
||||
}
|
||||
|
||||
private void checkValidation() {
|
||||
@@ -72,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);
|
||||
@@ -100,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);
|
||||
|
||||
@@ -20,24 +20,25 @@ package org.jackhuang.hmcl.upgrade;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
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.Schedulers;
|
||||
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;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.*;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
@@ -50,29 +51,24 @@ import java.util.zip.GZIPInputStream;
|
||||
*/
|
||||
public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
private boolean launchNewerVersion(List<String> args, File jar) throws IOException, PrivilegedActionException {
|
||||
private void launchNewerVersion(List<String> args, File jar) throws IOException, ReflectiveOperationException {
|
||||
try (JarFile jarFile = new JarFile(jar)) {
|
||||
String mainClass = jarFile.getManifest().getMainAttributes().getValue("Main-Class");
|
||||
if (mainClass != null) {
|
||||
ArrayList<String> al = new ArrayList<>(args);
|
||||
al.add("--noupdate");
|
||||
ClassLoader pre = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
|
||||
Logging.stop();
|
||||
ClassLoader now = new URLClassLoader(new URL[]{jar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
|
||||
Thread.currentThread().setContextClassLoader(now);
|
||||
now.loadClass(mainClass).getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
Logging.start();
|
||||
Thread.currentThread().setContextClassLoader(pre);
|
||||
}
|
||||
return true;
|
||||
if (mainClass == null)
|
||||
throw new ClassNotFoundException("Main-Class not found in manifest");
|
||||
ArrayList<String> al = new ArrayList<>(args);
|
||||
al.add("--noupdate");
|
||||
ClassLoader pre = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Logging.stop();
|
||||
ClassLoader now = new URLClassLoader(new URL[]{jar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
|
||||
Thread.currentThread().setContextClassLoader(now);
|
||||
now.loadClass(mainClass).getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
|
||||
} finally {
|
||||
Logging.start(Launcher.LOG_DIRECTORY);
|
||||
Thread.currentThread().setContextClassLoader(pre);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,15 +84,18 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
String j = m.get("loc");
|
||||
if (j != null) {
|
||||
File jar = new File(j);
|
||||
if (jar.exists() && launchNewerVersion(args, jar))
|
||||
if (jar.exists()) {
|
||||
launchNewerVersion(args, jar);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JsonParseException ex) {
|
||||
f.delete();
|
||||
} catch (IOException | PrivilegedActionException t) {
|
||||
} catch (IOException | ReflectiveOperationException t) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to execute newer version application", t);
|
||||
AppDataUpgraderPackGzTask.HMCL_VER_FILE.delete(); // delete version json, let HMCL re-download the newer version.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,56 +107,57 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
checker.requestDownloadLink().then(Task.of(variables -> {
|
||||
Map<String, String> map = variables.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID);
|
||||
|
||||
if (MessageBox.confirm(Launcher.i18n("update.newest_version", version.toString()) + "\n"
|
||||
+ Launcher.i18n("update.should_open_link"),
|
||||
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION)
|
||||
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("jarsha1"))
|
||||
hash = map.get("jarsha1");
|
||||
Controllers.dialog(Launcher.i18n("message.downloading"));
|
||||
if (new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash).test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.closeDialog();
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else if (map != null && map.containsKey("pack") && !StringUtils.isBlank(map.get("pack")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("packsha1"))
|
||||
hash = map.get("packsha1");
|
||||
Task task = new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash);
|
||||
TaskExecutor executor = task.executor();
|
||||
JFXUtilities.runInFX(() -> Controllers.taskDialog(executor, Launcher.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);
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else {
|
||||
String url = Launcher.PUBLISH;
|
||||
if (map != null)
|
||||
if (map.containsKey(OperatingSystem.CURRENT_OS.getCheckedName()))
|
||||
url = map.get(OperatingSystem.CURRENT_OS.getCheckedName());
|
||||
else if (map.containsKey(OperatingSystem.UNKNOWN.getCheckedName()))
|
||||
url = map.get(OperatingSystem.UNKNOWN.getCheckedName());
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(url));
|
||||
} 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"));
|
||||
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("jarsha1"))
|
||||
hash = map.get("jarsha1");
|
||||
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, 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(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else if (map != null && map.containsKey("pack") && !StringUtils.isBlank(map.get("pack")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("packsha1"))
|
||||
hash = map.get("packsha1");
|
||||
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, 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(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else {
|
||||
String url = Launcher.PUBLISH;
|
||||
if (map != null)
|
||||
if (map.containsKey(OperatingSystem.CURRENT_OS.getCheckedName()))
|
||||
url = map.get(OperatingSystem.CURRENT_OS.getCheckedName());
|
||||
else if (map.containsKey(OperatingSystem.UNKNOWN.getCheckedName()))
|
||||
url = map.get(OperatingSystem.UNKNOWN.getCheckedName());
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(url));
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e);
|
||||
OperatingSystem.setClipboard(url);
|
||||
MessageBox.show(i18n("update.no_browser"));
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
@@ -185,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
|
||||
@@ -195,7 +195,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
if (!FileUtils.makeDirectory(f.getParentFile()))
|
||||
throw new IOException("Failed to make directories: " + f.getParent());
|
||||
|
||||
for (int i = 0; f.exists(); i++)
|
||||
for (int i = 0; f.exists() && !f.delete(); i++)
|
||||
f = new File(BASE_FOLDER, "HMCL-" + newestVersion + (i > 0 ? "-" + i : "") + ".jar");
|
||||
if (!f.createNewFile())
|
||||
throw new IOException("Failed to create new file: " + f);
|
||||
@@ -213,7 +213,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
public static class AppDataUpgraderJarTask extends Task {
|
||||
|
||||
public static final File BASE_FOLDER = Launcher.getWorkingDirectory("hmcl");
|
||||
public static final File BASE_FOLDER = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json");
|
||||
|
||||
public static File getSelf(String ver) {
|
||||
@@ -235,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
|
||||
|
||||
@@ -17,19 +17,26 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
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;
|
||||
import java.util.List;
|
||||
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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -51,8 +58,12 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
URL url = requestDownloadLink();
|
||||
if (url == null) return;
|
||||
File newf = new File(url.getFile());
|
||||
Controllers.dialog(Launcher.i18n("message.downloading"));
|
||||
if (new FileDownloadTask(url, newf).test()) {
|
||||
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, i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
try {
|
||||
new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath())
|
||||
.directory(new File("").getAbsoluteFile())
|
||||
@@ -62,7 +73,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.closeDialog();
|
||||
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
|
||||
}
|
||||
|
||||
private static String getRealPath() {
|
||||
@@ -70,7 +81,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
File file = new File(realPath);
|
||||
realPath = file.getAbsolutePath();
|
||||
try {
|
||||
realPath = java.net.URLDecoder.decode(realPath, Charsets.DEFAULT_CHARSET.name());
|
||||
realPath = java.net.URLDecoder.decode(realPath, UTF_8.name());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -22,14 +22,19 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.OutOfDateEvent;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -65,20 +70,29 @@ public final class UpdateChecker {
|
||||
*/
|
||||
public TaskResult<VersionNumber> process(final boolean showMessage) {
|
||||
return new TaskResult<VersionNumber>() {
|
||||
GetTask http = new GetTask(NetworkUtils.toURL(Launcher.UPDATE_SERVER + "/hmcl/update.php?version=" + Launcher.VERSION));
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return value == null ? Collections.singleton(http) : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (Launcher.VERSION.contains("@"))
|
||||
if (isDevelopmentVersion(Launcher.VERSION)) {
|
||||
LOG.info("Current version is a development version, skip updating");
|
||||
return;
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
versionString = NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update.php?version=" + Launcher.VERSION));
|
||||
versionString = http.getResult();
|
||||
value = VersionNumber.asVersion(versionString);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
Logging.LOG.warning("Unable to check update...");
|
||||
LOG.warning("Unable to check update...");
|
||||
if (showMessage)
|
||||
MessageBox.show(Launcher.i18n("update.failed"));
|
||||
MessageBox.show(i18n("update.failed"));
|
||||
} else if (base.compareTo(value) < 0)
|
||||
outOfDate = true;
|
||||
if (outOfDate)
|
||||
@@ -92,6 +106,11 @@ public final class UpdateChecker {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean isDevelopmentVersion(String version) {
|
||||
return version.contains("@") || // eg. @HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@
|
||||
version.contains("SNAPSHOT"); // eg. 3.1.SNAPSHOT
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the <b>cached</b> newest version number, use "process" method to
|
||||
* download!
|
||||
@@ -113,12 +132,13 @@ public final class UpdateChecker {
|
||||
return new TaskResult<Map<String, String>>() {
|
||||
@Override
|
||||
public void execute() {
|
||||
if (download_link == null)
|
||||
if (download_link == null) {
|
||||
try {
|
||||
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update_link.php")), Map.class);
|
||||
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL(Launcher.UPDATE_SERVER + "/hmcl/update_link.php")), Map.class);
|
||||
} catch (JsonSyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to get update link.", e);
|
||||
LOG.log(Level.SEVERE, "Failed to get update link.", e);
|
||||
}
|
||||
}
|
||||
setResult(download_link);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,17 @@ import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.ui.CrashWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import static java.util.Collections.newSetFromMap;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
@@ -34,19 +39,19 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
private static final HashMap<String, String> SOURCE = new HashMap<String, String>() {
|
||||
private static final Map<String, String> SOURCE = new HashMap<String, String>() {
|
||||
{
|
||||
put("javafx.fxml.LoadException", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("Location is not set", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("UnsatisfiedLinkError", Launcher.i18n("crash.user_fault"));
|
||||
put("java.lang.NoClassDefFoundError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.VerifyError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchMethodError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.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?");
|
||||
}
|
||||
};
|
||||
@@ -57,11 +62,11 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
if (s.contains(entry.getKey())) {
|
||||
if (StringUtils.isNotBlank(entry.getValue())) {
|
||||
String info = entry.getValue();
|
||||
Logging.LOG.severe(info);
|
||||
LOG.severe(info);
|
||||
try {
|
||||
MessageBox.show(info);
|
||||
} catch (Throwable t) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to show message", t);
|
||||
LOG.log(Level.SEVERE, "Unable to show message", t);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -69,17 +74,21 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Set<String> CAUGHT_EXCEPTIONS = newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread t, Throwable e) {
|
||||
String stackTrace = StringUtils.getStackTrace(e);
|
||||
if (!stackTrace.contains("org.jackhuang"))
|
||||
return;
|
||||
|
||||
if (THROWABLE_SET.contains(stackTrace))
|
||||
return;
|
||||
THROWABLE_SET.add(stackTrace);
|
||||
LOG.log(Level.SEVERE, "Uncaught exception in thread " + t.getName(), e);
|
||||
|
||||
try {
|
||||
String stackTrace = StringUtils.getStackTrace(e);
|
||||
if (!stackTrace.contains("org.jackhuang"))
|
||||
return;
|
||||
|
||||
if (CAUGHT_EXCEPTIONS.contains(stackTrace))
|
||||
return;
|
||||
CAUGHT_EXCEPTIONS.add(stackTrace);
|
||||
|
||||
String text = "---- Hello Minecraft! Crash Report ----\n" +
|
||||
" Version: " + Launcher.VERSION + "\n" +
|
||||
" Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" +
|
||||
@@ -91,21 +100,18 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
" Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor") + "\n" +
|
||||
" Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor") + "\n";
|
||||
|
||||
Logging.LOG.log(Level.SEVERE, text);
|
||||
LOG.log(Level.SEVERE, text);
|
||||
|
||||
if (checkThrowable(e) && !text.contains("OpenJDK")) {
|
||||
if (checkThrowable(e)) {
|
||||
Platform.runLater(() -> new CrashWindow(text).show());
|
||||
if (!Launcher.UPDATE_CHECKER.isOutOfDate())
|
||||
reportToServer(text);
|
||||
}
|
||||
} catch (Throwable ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to caught exception", ex);
|
||||
Logging.LOG.log(Level.SEVERE, "There is the original exception", e);
|
||||
} catch (Throwable handlingException) {
|
||||
LOG.log(Level.SEVERE, "Unable to handle uncaught exception", handlingException);
|
||||
}
|
||||
}
|
||||
|
||||
private static final HashSet<String> THROWABLE_SET = new HashSet<>();
|
||||
|
||||
private void reportToServer(final String text) {
|
||||
Thread t = new Thread(() -> {
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
@@ -113,11 +119,11 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
map.put("version", Launcher.VERSION);
|
||||
map.put("log", Logging.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/crash.php"), map);
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://hmcl.huangyuhui.net/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
Logging.LOG.log(Level.SEVERE, "Crash server response: " + response);
|
||||
LOG.log(Level.SEVERE, "Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to post HMCL server.", ex);
|
||||
LOG.log(Level.SEVERE, "Unable to post HMCL server.", ex);
|
||||
}
|
||||
});
|
||||
t.setDaemon(true);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
|
||||
public final class I18n {
|
||||
|
||||
private I18n() {}
|
||||
|
||||
private static ResourceBundle RESOURCE_BUNDLE = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Settings cannot be initialized", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceBundle getResourceBundle() {
|
||||
return RESOURCE_BUNDLE == null ? Locales.DEFAULT.getResourceBundle() : RESOURCE_BUNDLE;
|
||||
}
|
||||
|
||||
public static String i18n(String key, Object... formatArgs) {
|
||||
return String.format(i18n(key), formatArgs);
|
||||
}
|
||||
|
||||
public static String i18n(String key) {
|
||||
try {
|
||||
return getResourceBundle().getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+3
-4
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -15,9 +15,8 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.ui.construct.UTF8Control;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.List;
|
||||
@@ -53,7 +52,7 @@ public final class Locales {
|
||||
*/
|
||||
public static final SupportedLocale RU = new SupportedLocale(new Locale("ru"));
|
||||
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN);
|
||||
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN, ZH);
|
||||
|
||||
public static SupportedLocale getLocale(int index) {
|
||||
return Lang.get(LOCALES, index).orElse(DEFAULT);
|
||||
+8
-6
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -15,7 +15,9 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -26,14 +28,14 @@ import java.util.Locale;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public final class UTF8Control extends ResourceBundle.Control {
|
||||
final class UTF8Control extends ResourceBundle.Control {
|
||||
|
||||
public static final UTF8Control INSTANCE = new UTF8Control();
|
||||
|
||||
private UTF8Control() {}
|
||||
|
||||
@Override
|
||||
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
|
||||
throws IOException {
|
||||
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IOException {
|
||||
// The below is a copy of the default implementation.
|
||||
String bundleName = toBundleName(baseName, locale);
|
||||
String resourceName = toResourceName(bundleName, "properties");
|
||||
@@ -54,7 +56,7 @@ public final class UTF8Control extends ResourceBundle.Control {
|
||||
if (stream != null) {
|
||||
try {
|
||||
// Only this line is changed to make it to read properties files as UTF-8.
|
||||
bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
|
||||
bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8));
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
@@ -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,
|
||||
@@ -911,6 +910,22 @@
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.small-spinner {
|
||||
-jfx-radius: 10;
|
||||
}
|
||||
|
||||
.small-spinner > .arc {
|
||||
-fx-stroke-width: 3.0;
|
||||
}
|
||||
|
||||
.small-spinner-pane .jfx-spinner {
|
||||
-jfx-radius: 10;
|
||||
}
|
||||
|
||||
.small-spinner-pane .jfx-spinner > .arc {
|
||||
-fx-stroke-width: 3.0;
|
||||
}
|
||||
|
||||
.second-spinner {
|
||||
-jfx-radius: 30;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
type="StackPane">
|
||||
@@ -25,18 +26,18 @@
|
||||
|
||||
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.columnSpan="2"/>
|
||||
|
||||
<Label fx:id="lblAddInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT"
|
||||
<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="linkAddInjectorServer" text="%account.injector.add"
|
||||
onMouseClicked="#onAddInjecterServer" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
|
||||
<Hyperlink fx:id="linkManageInjectorServers" text="%account.injector.manage"
|
||||
onMouseClicked="#onManageInjecterServers" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
|
||||
|
||||
<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>
|
||||
@@ -46,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>
|
||||
@@ -56,9 +57,10 @@
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning"/>
|
||||
<JFXButton onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
|
||||
<SpinnerPane fx:id="acceptPane" styleClass="small-spinner-pane">
|
||||
<JFXButton fx:id="btnAccept" onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
|
||||
</SpinnerPane>
|
||||
<JFXButton onMouseClicked="#onCreationCancel" text="%button.cancel" styleClass="dialog-cancel"/>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER"/>
|
||||
</fx:root>
|
||||
@@ -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,14 +1,12 @@
|
||||
<?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 com.jfoenix.controls.*?>
|
||||
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<JFXSpinner fx:id="spinner" style="-fx-radius:16" styleClass="materialDesign-purple, first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fx:id="scrollPane" fitToWidth="true" fitToHeight="true">
|
||||
<VBox fx:id="listPane" spacing="10" style="-fx-padding: 20 20 70 20;">
|
||||
@@ -23,62 +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="txtServerIp" promptText="%account.injector.server_ip">
|
||||
<validators>
|
||||
<URLValidator message="%input.url">
|
||||
</URLValidator>
|
||||
<URLValidator message="%input.url.http">
|
||||
<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" />
|
||||
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
|
||||
</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_ip" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
|
||||
<Label fx:id="lblServerIp" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label fx:id="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>
|
||||
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER" />
|
||||
</StackPane>
|
||||
</JFXDialog>
|
||||
</fx:root>
|
||||
@@ -8,6 +8,8 @@
|
||||
<?import org.jackhuang.hmcl.ui.construct.AdvancedListBox?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<?import java.lang.String?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
type="StackPane"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
@@ -44,6 +46,9 @@
|
||||
</StackPane>
|
||||
</center>
|
||||
</BorderPane>
|
||||
<ImageView fx:id="welcomeView">
|
||||
<Image url="/assets/img/welcome.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
</center>
|
||||
<top>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<ScrollPane fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%settings.choose_gamedir"/>
|
||||
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%profile.instance_directory.choose"/>
|
||||
</ComponentList>
|
||||
|
||||
</VBox>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane" pickOnBounds="false" FXUtils.limitWidth="160" FXUtils.limitHeight="156">
|
||||
<VBox fx:id="content" pickOnBounds="false">
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS" pickOnBounds="false" style="-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;">
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblGameVersion" style="-fx-font-size: 11;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblLibraries" style="-fx-font-size: 10; -fx-text-fill: gray;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40" pickOnBounds="false">
|
||||
<BorderPane>
|
||||
<left>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnSettings" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnUpdate" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnScript" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnLaunch" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minHeight="30" minWidth="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
</StackPane>
|
||||
</VBox>
|
||||
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT" pickOnBounds="false">
|
||||
<ImageView fx:id="iconView" StackPane.alignment="CENTER_RIGHT">
|
||||
<StackPane.margin>
|
||||
<Insets right="12" />
|
||||
</StackPane.margin>
|
||||
<Image url="/assets/img/icon.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -25,7 +25,7 @@
|
||||
<MultiFileItem fx:id="javaItem" title="%settings.game.java_directory" chooserTitle="%settings.game.java_directory.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" directory="false" />
|
||||
|
||||
<MultiFileItem fx:id="gameDirItem" title="%profile.instance_directory" chooserTitle="%settings.choose_gamedir"
|
||||
<MultiFileItem fx:id="gameDirItem" title="%settings.game.game_directory" chooserTitle="%settings.game.game_directory.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" directory="true" />
|
||||
|
||||
<BorderPane> <!-- Max Memory -->
|
||||
@@ -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 |
@@ -18,11 +18,12 @@
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=Author
|
||||
about.author.statement=huanghongxun (MCF: klkl6523)
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=Thanks to
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
|
||||
about.dependency=Dependency
|
||||
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors who participated in this project via issues, pull requests, etc.
|
||||
about.dependency=Dependencies
|
||||
# Due to space limitations, only first authors are listed
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
|
||||
about.claim=Statement
|
||||
about.claim.statement=Minecraft is copyrighted by Mojang AB. We are not responsible for any copyright infringement arising from the use of this software.
|
||||
about.open_source=Open Source
|
||||
@@ -31,25 +32,28 @@ about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
account=Accounts
|
||||
account.character=character
|
||||
account.choose=Choose a character
|
||||
account.current=Current
|
||||
account.create=Create a new account
|
||||
account.email=Email
|
||||
account.failed.connect_authentication_server=Cannot connect the authentication server. Check your network.
|
||||
account.failed.invalid_credentials=Incorrect password. Or forbidden to log in temporarily.
|
||||
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your network.
|
||||
account.failed.invalid_credentials=Incorrect password, or you are forbidden to login temporarily.
|
||||
account.failed.invalid_password=Invalid password
|
||||
account.failed.invalid_token=Please log out and re-input your password to log in.
|
||||
account.failed.no_charactor=No character in this account.
|
||||
account.injector.add=Add new authentication server
|
||||
account.failed.invalid_token=Please log out and re-input your password to login.
|
||||
account.failed.no_character=No character in this account.
|
||||
account.failed.no_selected_server=No authentication server is selected.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
account.injector.add=Add 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_ip=Server URL
|
||||
account.injector.server_url=Server URL
|
||||
account.injector.server_name=Server Name
|
||||
account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
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
|
||||
|
||||
@@ -58,73 +62,39 @@ archive.game_version=Game
|
||||
archive.version=Version
|
||||
|
||||
assets.download=Download assets
|
||||
assets.download_all=Check for the completion of assets
|
||||
assets.failed=Failed to get the list, try again.
|
||||
assets.failed_download=Failed to download assets, may cause no sounds and language files.
|
||||
assets.download_all=Check the integrity of assets
|
||||
|
||||
button.about=About
|
||||
button.cancel=Cancel
|
||||
button.clear=Clear
|
||||
button.close=Close
|
||||
button.copy=Copy
|
||||
button.delete=Delete
|
||||
button.download=Download
|
||||
button.edit=Edit
|
||||
button.explore=Explore
|
||||
button.info=Info
|
||||
button.install=Install
|
||||
button.more=More
|
||||
button.no=No
|
||||
button.ok=OK
|
||||
button.others=Others
|
||||
button.preview=Preview
|
||||
button.refresh=Refresh
|
||||
button.retry=Retry
|
||||
button.save=Save
|
||||
button.test=Test
|
||||
button.yes=Yes
|
||||
|
||||
color.blue=Blue
|
||||
color.dark_blue=Dark Blue
|
||||
color.green=Green
|
||||
color.orange=Orange
|
||||
color.purple=Purple
|
||||
color.red=Red
|
||||
color.recent=Recommended
|
||||
color.custom=Custom Color
|
||||
|
||||
crash.NoClassDefFound=Please check "HMCL" software is complete.
|
||||
crash.advice.ClassNotFoundException=Minecraft or mods are incomplete. Retry if there are some libraries that have not downloaded or update your game and mods! Or you can try Game Settings -> Manage (Version) -> Delete library files to solve the problem.
|
||||
crash.advice.ConcurrentModificationException=Maybe your Java is newer than 1.8.0_11, you could downgrade to Java 7.
|
||||
crash.advice.LWJGLException=Maybe your video driver does not work well, please update your video driver.
|
||||
crash.advice.NoSuchFieldError=Minecraft or mods are incomplete. Retry if there are some libraries that have not downloaded or update your game and mods!
|
||||
crash.advice.OpenGL=Maybe drivers caused problems.
|
||||
crash.advice.OutOfMemoryError=The maximum memory of JVM is too small, please modify it.
|
||||
crash.advice.SecurityException=Maybe you have modified minecraft.jar but have not removed the META-INF.
|
||||
crash.advice.UnsupportedClassVersionError=Maybe your java is too old, try to update the java.
|
||||
crash.advice.no=No advice.
|
||||
crash.advice.no_lwjgl=Maybe drivers caused problems.
|
||||
crash.advice.otherwise=Maybe mods caused problems.
|
||||
crash.error=Minecraft has crashed.
|
||||
crash.headless=If your OS is Linux, please use Oracle JDK instead of OpenJDK, or add "-Djava.awt.headless=false" JVM argument, or check if your Xserver works normally.
|
||||
crash.launcher=Launcher has crashed!
|
||||
crash.minecraft=Minecraft has crashed!
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
|
||||
|
||||
download=Download
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
|
||||
download.BMCL=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
download.failed=Failed to download
|
||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||
download.mojang=Mojang
|
||||
download.not_200=Failed to download, the response code is %s.
|
||||
download.source=Download Source
|
||||
download.successfully=Downloaded successfully
|
||||
|
||||
extension.bat=Windows Bat file
|
||||
extension.mod=Mod file
|
||||
extension.png=Image file
|
||||
extension.sh=Bash shell
|
||||
|
||||
fatal.missing_javafx=JavaFX is missing.\nIf you are using Java 11 or later, please downgrade to Java 8 or 10.\nIf you are using OpenJDK, please ensure OpenJFX is included.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use HMCL, but HMCL will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause HMCL not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
|
||||
|
||||
folder.config=Configs
|
||||
folder.coremod=Core Mod
|
||||
folder.game=Game Dir
|
||||
@@ -133,16 +103,16 @@ folder.resourcepacks=Resourcepacks
|
||||
folder.saves=Saves
|
||||
folder.screenshots=Screenshots
|
||||
|
||||
general.prompt.empty=(None)
|
||||
|
||||
input.email=The username must be an e-mail.
|
||||
input.number=Must be a number.
|
||||
input.not_empty=Input Requrired!
|
||||
input.not_empty=Required field
|
||||
input.url=Must be a valid URL.
|
||||
input.url.http=Only Http or Https accepted.
|
||||
|
||||
install=Install New Game
|
||||
install.failed=Failed to install
|
||||
install.installer.choose=Choose a %s version
|
||||
install.installer.failed=Unable to install %s.
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=Game
|
||||
install.installer.install=
|
||||
@@ -154,37 +124,29 @@ install.modpack=Install a modpack (CurseForge supported)
|
||||
install.new_game=Install a New Game
|
||||
install.new_game.already_exists=This version has already been existing.
|
||||
install.new_game.current_game_version=Current Game Version
|
||||
install.no_version=The version is not found.
|
||||
install.no_version_if_intall=The required version is not found, do you wish to install the version automatically?
|
||||
install.release_time=Release Time
|
||||
install.select=Select an operation
|
||||
install.success=Installed successfully
|
||||
install.time=Time
|
||||
install.type=Type
|
||||
|
||||
lang=English
|
||||
lang.default=Belong to OS language.
|
||||
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.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or newer Java version.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
|
||||
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or later Java versions.
|
||||
launch.advice.newer_java=Java 8 is suggested, which can make game run more fluently. And many mods and Minecraft 1.12 and newer versions requires Java 8.
|
||||
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game probably crash. The launcher will try to launch it.
|
||||
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game probably crash. The maximum memory is 1024MB. The launcher will try to launch it.
|
||||
launch.circular_dependency_versions=Found circular dependency versions, please check if your client has been modified.
|
||||
launch.failed=Unable to launch
|
||||
launch.failed.cannot_create_jvm=Java virtual machine cannot be created. Java arguments may have problems. You can enable the no args mode in the settings.
|
||||
launch.failed.creating_process=Failed to create process, maybe your java path is wrong, please modify your java path.
|
||||
launch.failed.decompressing_natives=Unable to decompress native libraries.
|
||||
launch.failed.downloading_libraries=Did not finish downloading libraries, continue launching game?
|
||||
launch.failed.executable_permission=Unable to add permission to the launch script
|
||||
launch.failed.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
|
||||
launch.failed.prelaunch_command=Prelaunch command fail.
|
||||
launch.state.dependencies=Dependencies
|
||||
launch.state.done=Done
|
||||
launch.state.logging_in=Logging In
|
||||
launch.state.modpack=Preparing for modpack
|
||||
launch.state.waiting_launching=Waiting for game launching
|
||||
launch.unsupported_launcher_version=Sorry, the launcher cannot launch minecraft, but will retry launching it.
|
||||
launch.wrong_javadir=Incorrect Java directory, will reset to default Java directory.
|
||||
|
||||
launcher=Launcher
|
||||
@@ -196,62 +158,31 @@ launcher.common_directory.choose=Choose common directory.
|
||||
launcher.contact=Contact Us
|
||||
launcher.crash=Hello Minecraft! Launcher has crashed!
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
|
||||
launcher.exit_failed=Failed to shutdown.
|
||||
launcher.modpack=Documentations for modpacks.
|
||||
launcher.open_jdk=We have found that you started this application using OpenJDK, which will cause so many troubles drawing the UI. We suggest you using Oracle JDK instead.
|
||||
launcher.restart=Options will be in operations only if restart this app.
|
||||
launcher.tab.about=About
|
||||
launcher.tab.general=General
|
||||
launcher.tab.ui=UI
|
||||
launcher.update_java=Please upgrade your Java.
|
||||
launcher.update_launcher=Check for update
|
||||
launcher.versions_json_not_formatted=The version information of %s is malformed! Redownload it?
|
||||
launcher.versions_json_not_matched=The version %s is malformed! There are a json:%s in this version. Do you want to fix this problem?
|
||||
launcher.versions_json_not_matched_cannot_auto_completion=The version %s lost version information file, delete it?
|
||||
|
||||
login.changed_client_token=The server response has changed the client token.
|
||||
login.choose_charactor=Please choose the character you want
|
||||
login.empty_username=You have not set a username!
|
||||
login.enter_password=Please enter your password.
|
||||
login.enter_username=Please enter your name.
|
||||
login.failed=Failed to login
|
||||
login.invalid_username=Invalid username
|
||||
login.invalid_uuid_and_username=Invalid UUID and username
|
||||
login.no_valid_character=No valid character, please visit skinme.cc and create your own character.
|
||||
|
||||
logwindow.search=Search
|
||||
logwindow.show_lines=Show Lines
|
||||
logwindow.terminate_game=Terminate Game
|
||||
logwindow.title=Log
|
||||
|
||||
main_page=Home
|
||||
|
||||
message.cancelled=Cancelled
|
||||
message.cannot_open_explorer=Cannot open explorer:
|
||||
message.confirm=Confirm
|
||||
message.doing=Please wait
|
||||
message.downloading=Downloading...
|
||||
message.error=Error
|
||||
message.info=Info
|
||||
message.loading=Loading...
|
||||
message.success=Tasks succeeded
|
||||
message.unknown=Unknown
|
||||
|
||||
minecraft.invalid=Invalid
|
||||
minecraft.invalid_jar=Invalid Jar
|
||||
minecraft.modified=(Modified!)
|
||||
minecraft.not_a_file=Not a file
|
||||
minecraft.not_found=Not found
|
||||
minecraft.not_readable=Not readable
|
||||
minecraft.wrong_path=Wrong Minecraft path, the launcher could not find the path.
|
||||
|
||||
modpack=Mod pack
|
||||
modpack.choose=Choose a modpack zip file which you want to import. If you want to update the modpack, please enter the version you want to update.
|
||||
modpack.choose=Choose a modpack zip file which you want to install.
|
||||
modpack.desc=Describe your modpack, including precautions, changlog, supporting Markdown(also supporting online pictures).
|
||||
modpack.enter_name=Enter your desired name for this game.
|
||||
modpack.export=Export Modpack
|
||||
modpack.export_error=Failed to export the modpack, maybe the format of your game directory is incorrect or failed to manage files.
|
||||
modpack.export_finished=Exporting the modpack finished. See
|
||||
modpack.files.blueprints=BuildCraft blueprints
|
||||
modpack.files.config=Mod configs
|
||||
modpack.files.dumps=NEI debug output
|
||||
@@ -264,22 +195,20 @@ modpack.files.resourcepacks=Resource(Texture) packs
|
||||
modpack.files.saves=Saved games
|
||||
modpack.files.scripts=MineTweaker configuration
|
||||
modpack.files.servers_dat=Multiplayer servers list
|
||||
modpack.included_launcher=The modpack is included the launcher, you can publish it directly.
|
||||
modpack.install=Install %s modpack
|
||||
modpack.installing=Installing modpack
|
||||
modpack.introduction=Curse, MultiMC, HMCL, MCBBS modpacks supprted.
|
||||
modpack.invalid=Invalid modpack file.
|
||||
modpack.mismatched_type=Mismatched modpack type, your current game is %s modpack, but your update file is %s modpack.
|
||||
modpack.name=Modpack Name
|
||||
modpack.not_a_valid_location=Not a valid modpack path
|
||||
modpack.not_a_valid_name=Not a valid modpack name
|
||||
modpack.not_included_launcher=You can use the modpack by clicking the "Import Modpack" button.
|
||||
modpack.scan=Scanning this modpack
|
||||
modpack.task.install=Import Modpack
|
||||
modpack.task.install.error=Failed to install the modpack. Maybe the files is incorrect, or a management issue occurred.
|
||||
modpack.task.install.will=Install the modpack:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=Install relative files to Curse modpack
|
||||
modpack.type.curse.error=Unable to complete this Curse modpack. Please retry.
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=Unsupported modpack, only HMCL, MultiMC, Curse modpacks are supported.
|
||||
@@ -290,7 +219,6 @@ modpack.wizard.step.1.title=Set the basic options to the modpack.
|
||||
modpack.wizard.step.2=Files selection
|
||||
modpack.wizard.step.2.title=Choose the files you want to put into the modpack.
|
||||
modpack.wizard.step.3=Description
|
||||
modpack.wizard.step.3.title=Describe your modpack.
|
||||
modpack.wizard.step.initialization.exported_version=The exported game version
|
||||
modpack.wizard.step.initialization.include_launcher=Include the launcher
|
||||
modpack.wizard.step.initialization.save=Choose a path to export the game files to
|
||||
@@ -301,19 +229,15 @@ mods.add=Add mods
|
||||
mods.add.failed=Failed to add mods %s.
|
||||
mods.add.success=Successfully added mods %s.
|
||||
mods.choose_mod=Choose your mods
|
||||
mods.default_information=Please ensure that you have installed Forge or LiteLoader before installing mods!\nYou can drop your mod files from explorer/finder, and delete mods by the delete button.\nDisable a mod by leaving the check box unchecked; Choose an item to get the information.
|
||||
mods.remove=Remove
|
||||
|
||||
operation.confirm_stop=Terminate the operations?
|
||||
operation.stopped=The operation was aborted.
|
||||
|
||||
profile=Profile
|
||||
profile.default=Current directory
|
||||
profile.home=User home
|
||||
profile.instance_directory=Game Directory
|
||||
profile.instance_directory.choose=Choose Game Directory
|
||||
profile.new=New Config
|
||||
profile.new_name=New Profile Name:
|
||||
profile.remove=Sure to remove profile %s?
|
||||
profile.title=Game Directories
|
||||
profile.selected=Selected
|
||||
|
||||
@@ -323,7 +247,7 @@ selector.custom=Custom
|
||||
|
||||
settings=Settings
|
||||
|
||||
settings.advanced=Advanced
|
||||
settings.advanced=Advanced Settings
|
||||
settings.advanced.dont_check_game_completeness=Don't check game completeness
|
||||
settings.advanced.game_dir.default=Default (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Independent (.minecraft/versions/<version name>/, except assets,libraries)
|
||||
@@ -342,16 +266,14 @@ settings.advanced.precall_command=Pre-Launch command(will be executed before gam
|
||||
settings.advanced.server_ip=Server Host
|
||||
settings.advanced.wrapper_launcher=Wrapper Launcher(i.e. optirun...)
|
||||
|
||||
settings.cannot_remove_default_config=Cannot remove the default configution.
|
||||
settings.choose_gamedir=Choose Game Directory
|
||||
settings.custom=Custom
|
||||
settings.default=Default
|
||||
settings.failed_load=Failed to load settings file. Remove it?
|
||||
|
||||
settings.game=Games
|
||||
settings.game.dimension=Game Window Dimension
|
||||
settings.game.exploration=Explore
|
||||
settings.game.fullscreen=Fullscreen
|
||||
settings.game.game_directory=Game Directory
|
||||
settings.game.game_directory.choose=Choose Game Directory
|
||||
settings.game.java_directory=Java Dir
|
||||
settings.game.java_directory.choose=Choose Java Directory.
|
||||
settings.game.management=Manage
|
||||
@@ -377,24 +299,18 @@ settings.launcher.theme=Theme
|
||||
|
||||
settings.max_memory=Max Memory/MB
|
||||
settings.physical_memory=Physical Memory Size
|
||||
settings.run_directory=Run Directory(Version Isolation)
|
||||
settings.show_log=Show Logs
|
||||
settings.tabs.assets_downloads=Assets
|
||||
settings.tabs.game_download=Games
|
||||
settings.tabs.installers=Installers
|
||||
settings.test_game=Test game
|
||||
settings.type.global=Global version settings(all shared)
|
||||
settings.type=Version setting type
|
||||
settings.type.special=Specialized version settings(will not affect other versions)
|
||||
settings.update_version=Update version json.
|
||||
|
||||
update=Update
|
||||
update.failed=Failed to check for updates.
|
||||
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.should_open_link=Are you willing to update the launcher?
|
||||
update.tooltip=Update
|
||||
|
||||
version.cannot_read=Unable to gather the game version. Cannot continue auto-installing.
|
||||
@@ -408,7 +324,6 @@ version.launch_script.failed=Unable to make launch script.
|
||||
version.launch_script.save=Save the launch script
|
||||
version.launch_script.success=Finished script creation, %s.
|
||||
version.manage.redownload_assets_index=Redownload Assets Index
|
||||
version.manage.redownload_json=Redownload Minecraft Configuration(minecraft.json)
|
||||
version.manage.remove=Delete this version
|
||||
version.manage.remove.confirm=Sure to remove version %s?
|
||||
version.manage.remove_libraries=Delete library files
|
||||
@@ -418,10 +333,6 @@ version.settings=Settings
|
||||
version.update=Update
|
||||
|
||||
wizard.prev=< Prev
|
||||
wizard.close=Close
|
||||
wizard.failed=Failed
|
||||
wizard.finish=Finish
|
||||
wizard.help=Help
|
||||
wizard.next=Next >
|
||||
wizard.steps=Steps
|
||||
wizard.summary=Summary
|
||||
@@ -0,0 +1,338 @@
|
||||
# Hello Minecraft! Launcher.
|
||||
# Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
#
|
||||
#author: huangyuhui
|
||||
about.copyright=版權
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鳴謝
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默認背景圖)\n所有通過 Issues、Pull Requests 等方式參與本項目的貢獻者
|
||||
about.dependency=依賴
|
||||
# 由於篇幅限制,僅列出第一作者
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
|
||||
about.claim=免責聲明
|
||||
about.claim.statement=Minecraft 版權歸 Mojang AB 所有,使用本軟件產生的版權問題,軟件製作方概不負責。請支持正版。
|
||||
about.open_source=開源
|
||||
about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
|
||||
account=賬戶
|
||||
account.character=角色
|
||||
account.choose=選擇一個角色
|
||||
account.create=新建賬戶
|
||||
account.email=郵箱
|
||||
account.failed.connect_authentication_server=無法連接認證服務器,可能是網絡問題
|
||||
account.failed.invalid_credentials=您的用戶名或密碼錯誤,或者登錄次數過多被暫時禁止登錄,請稍後再試
|
||||
account.failed.invalid_password=無效的密碼
|
||||
account.failed.invalid_token=請嘗試登出並重新輸入密碼登錄
|
||||
account.failed.no_character=該帳號沒有角色
|
||||
account.failed.no_selected_server=未選擇認證服務器
|
||||
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
|
||||
account.injector.add=添加認證服務器
|
||||
account.injector.manage=管理認證服務器
|
||||
account.injector.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=已選中
|
||||
|
||||
selector.choose=選擇
|
||||
selector.choose_file=選擇文件
|
||||
selector.custom=自定義
|
||||
|
||||
settings=普通設置
|
||||
|
||||
settings.advanced=高級設置
|
||||
settings.advanced.dont_check_game_completeness=不檢查遊戲完整性
|
||||
settings.advanced.game_dir.default=默認(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本獨立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_args_default=啓動器默認添加的參數(請不要重複添加):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=內存永久保存區域(不必填寫,MB)
|
||||
settings.advanced.jvm_args=Java 虛擬機參數(不必填寫)
|
||||
settings.advanced.launcher_visibility.close=遊戲啓動後結束啓動器
|
||||
settings.advanced.launcher_visibility.hide=遊戲啓動後隱藏啓動器
|
||||
settings.advanced.launcher_visibility.hide_reopen=隱藏啓動器並在遊戲結束後重新打開
|
||||
settings.advanced.launcher_visibility.keep=保持啓動器可見
|
||||
settings.advanced.launcher_visible=啓動器可見性
|
||||
settings.advanced.minecraft_arguments=Minecraft 額外參數(不必填寫)
|
||||
settings.advanced.no_common=不使用公共路徑
|
||||
settings.advanced.no_jvm_args=不添加默認的 JVM 參數(使用 Java 9 時必勾)
|
||||
settings.advanced.precall_command=啓動前執行命令(不必填寫,將在遊戲啓動前調用)
|
||||
settings.advanced.server_ip=直入服務器 IP 地址(不必填寫,啓動遊戲後直接進入對應服務器)
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填寫,如 optirun)
|
||||
|
||||
settings.custom=自定義
|
||||
|
||||
settings.game=遊戲設置
|
||||
settings.game.dimension=遊戲窗口分辨率
|
||||
settings.game.exploration=瀏覽
|
||||
settings.game.fullscreen=全屏
|
||||
settings.game.game_directory=遊戲路徑(版本隔離)
|
||||
settings.game.game_directory.choose=選擇遊戲路徑
|
||||
settings.game.java_directory=Java 路徑
|
||||
settings.game.java_directory.choose=選擇 Java 路徑
|
||||
settings.game.management=管理
|
||||
|
||||
settings.icon=遊戲圖標
|
||||
|
||||
settings.launcher=啓動器設置
|
||||
settings.launcher.common_path.tooltip=啓動器將所有遊戲資源及依賴庫文件放於此集中管理,如果遊戲文件夾內有現成的將不會使用公共庫文件
|
||||
settings.launcher.download_source=下載源
|
||||
settings.launcher.language=語言
|
||||
settings.launcher.log_font=日誌字體
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=代理賬戶
|
||||
settings.launcher.proxy.no_proxy=直連
|
||||
settings.launcher.proxy.has_proxy=啓用驗證
|
||||
settings.launcher.proxy.host=主機
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密碼
|
||||
settings.launcher.proxy.port=端口
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=賬戶
|
||||
settings.launcher.theme=主題
|
||||
|
||||
settings.max_memory=最大內存(MB)
|
||||
settings.physical_memory=物理內存大小
|
||||
settings.show_log=查看日誌
|
||||
settings.tabs.installers=自動安裝
|
||||
settings.type.global=全局版本設置(使用該設置的版本共用一套設定)
|
||||
settings.type=版本設置類型
|
||||
settings.type.special=單獨版本設置(不會影響到其他版本的設定)
|
||||
|
||||
update=啓動器更新
|
||||
update.failed=檢查更新失敗
|
||||
update.found=發現更新
|
||||
update.newest_version=最新版本爲:%s
|
||||
update.latest=當前版本爲最新版本
|
||||
update.no_browser=無法打開瀏覽器,網址已經複製到剪貼板了,您可以手動粘貼網址打開頁面
|
||||
update.tooltip=更新
|
||||
|
||||
version.cannot_read=讀取遊戲版本失敗,無法進行自動安裝
|
||||
version.forbidden_name=此版本名稱不受支持,請換一個名字
|
||||
version.game.old=遠古版
|
||||
version.game.release=穩定版
|
||||
version.game.snapshot=測試版
|
||||
version.launch=啓動遊戲
|
||||
version.launch_script=生成啓動腳本
|
||||
version.launch_script.failed=生成啓動腳本失敗
|
||||
version.launch_script.save=保存啓動腳本
|
||||
version.launch_script.success=啓動腳本已生成完畢:%s
|
||||
version.manage.redownload_assets_index=重新下載資源配置(assets_index.json)
|
||||
version.manage.remove=刪除該版本
|
||||
version.manage.remove.confirm=真的要刪除版本 %s 嗎?
|
||||
version.manage.remove_libraries=刪除所有庫文件
|
||||
version.manage.rename=重命名該版本
|
||||
version.manage.rename.message=請輸入要改成的名字
|
||||
version.settings=遊戲設置
|
||||
version.update=更新
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.failed=失敗
|
||||
wizard.finish=完成
|
||||
wizard.next=下一步 >
|
||||
@@ -18,11 +18,12 @@
|
||||
about.copyright=版权
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (百度贴吧:huanghongxun20)
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鸣谢
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, https://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有通过 Issues、Pull Requests 等方式参与本项目的贡献者
|
||||
about.dependency=依赖
|
||||
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
|
||||
# 由于篇幅限制,仅列出第一作者
|
||||
about.dependency.statement=JFoenix (Shadi Shaheen, Apache 2.0)\nGson (Google, Apache 2.0)\nApache Commons Compress (ASF, Apache 2.0)\nXZ for Java (Lasse Collin, Public Domain)\nfx-gson (Joffrey Bion, MIT)
|
||||
about.claim=免责声明
|
||||
about.claim.statement=Minecraft 版权归 Mojang AB 所有,使用本软件产生的版权问题,软件制作方概不负责。请支持正版。
|
||||
about.open_source=开源
|
||||
@@ -31,18 +32,21 @@ about.open_source.statement=GPL v3 (https://github.com/huanghongxun/HMCL/)
|
||||
account=账户
|
||||
account.character=角色
|
||||
account.choose=选择一个角色
|
||||
account.current=当前账户
|
||||
account.create=新建账户
|
||||
account.email=邮箱
|
||||
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
|
||||
account.failed.invalid_credentials=您的用户名或密码错误,或者登录次数过多被暂时禁止登录,请稍后再试
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.no_charactor=该帐号没有角色
|
||||
account.injector.add=添加登录认证服务器
|
||||
account.injector.http=警告:此服务器使用不安全的http协议,您的密码在登录时会被明文传输。
|
||||
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_ip=服务器地址
|
||||
account.injector.server_url=服务器地址
|
||||
account.injector.server_name=服务器名称
|
||||
account.methods=登录方式
|
||||
account.methods.authlib_injector=authlib-injector 登录
|
||||
@@ -59,72 +63,38 @@ archive.version=版本
|
||||
|
||||
assets.download=下载资源
|
||||
assets.download_all=检查资源文件完整性
|
||||
assets.failed=获取列表失败,请刷新重试。
|
||||
assets.failed_download=下载资源文件失败,可能导致没有中文和声音。
|
||||
|
||||
button.about=关于
|
||||
button.cancel=取消
|
||||
button.clear=清除
|
||||
button.close=关闭
|
||||
button.copy=复制
|
||||
button.delete=删除
|
||||
button.download=下载
|
||||
button.edit=修改
|
||||
button.explore=浏览
|
||||
button.info=信息
|
||||
button.install=安装
|
||||
button.more=更多
|
||||
button.no=否
|
||||
button.ok=确定
|
||||
button.others=其他
|
||||
button.preview=预览
|
||||
button.refresh=刷新
|
||||
button.retry=重试
|
||||
button.save=保存
|
||||
button.test=测试
|
||||
button.yes=是
|
||||
|
||||
color.blue=蓝色
|
||||
color.dark_blue=深蓝色
|
||||
color.green=绿色
|
||||
color.orange=橙色
|
||||
color.purple=紫色
|
||||
color.red=红色
|
||||
color.recent=推荐
|
||||
color.custom=自定义颜色
|
||||
|
||||
crash.NoClassDefFound=请确认HMCL本体是否完整
|
||||
crash.advice.ClassNotFoundException=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题,另可尝试游戏设置->(版本)管理->删除库文件解决问题
|
||||
crash.advice.ConcurrentModificationException=这可能是因为您的Java版本高于Java 1.8.0_11导致的,可以尝试卸载Java8安装Java7。
|
||||
crash.advice.LWJGLException=您的电脑不正常,可能需要使用驱动精灵或其他安装器更新显卡驱动。
|
||||
crash.advice.NoSuchFieldError=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题。
|
||||
crash.advice.OpenGL=可能是显卡/声卡驱动问题,也可能是Mod导致的问题。
|
||||
crash.advice.OutOfMemoryError=内存溢出,您设置的Minecraft最大内存过小,请调大!
|
||||
crash.advice.SecurityException=可能是您修改了minecraft.jar但未删除META-INF文件夹的原因。请通过压缩软件删除jar中的META-INF文件夹。
|
||||
crash.advice.UnsupportedClassVersionError=这可能是因为您的Java版本过于老旧,可以尝试更换最新Java并在版本设置的Java路径中设置.
|
||||
crash.advice.no=无建议。
|
||||
crash.advice.no_lwjgl=可能是游戏依赖库不完整或解压依赖库时出错。可以通过下载整合包解决问题。
|
||||
crash.advice.otherwise=可能是Mod或其他问题。
|
||||
crash.error=您的Minecraft崩溃了。
|
||||
crash.headless=如果您的操作系统是Linux,请注意不要使用OpenJDK,务必使用Oracle JDK,或尝试添加-Djava.awt.headless=false参数,或检查您的Xserver是否正常
|
||||
crash.launcher=启动器崩溃了!
|
||||
crash.minecraft=Minecraft崩溃了!请认真阅读建议。
|
||||
crash.user_fault=您的系统或Java环境可能安装不当导致本软件崩溃,请检查您的Java环境或您的电脑!可以尝试重新安装Java。
|
||||
crash.NoClassDefFound=请确认 HMCL 本体是否完整,或更新您的 Java。
|
||||
crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩溃,请检查您的 Java 环境或您的电脑!可以尝试重新安装 Java。
|
||||
|
||||
download=下载
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
|
||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.failed=下载失败
|
||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||
download.mojang=官方
|
||||
download.not_200=下载失败,HTTP状态码:%s.
|
||||
download.source=下载源
|
||||
download.successfully=下载完成
|
||||
|
||||
extension.bat=Windows 脚本
|
||||
extension.mod=模组文件
|
||||
extension.png=图片文件
|
||||
extension.sh=Bash 脚本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Let's Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
|
||||
folder.config=配置文件夹
|
||||
folder.coremod=核心MOD文件夹
|
||||
folder.game=游戏文件夹
|
||||
@@ -133,187 +103,141 @@ folder.resourcepacks=资源包文件夹
|
||||
folder.saves=存档文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
|
||||
general.prompt.empty=(无)
|
||||
|
||||
input.email=用户名必须是邮箱
|
||||
input.number=必须是数字
|
||||
input.not_empty=必填项
|
||||
input.url=必须是合法的链接
|
||||
input.url.http=只支持Http或Https协议
|
||||
|
||||
install=添加游戏
|
||||
install.failed=安装失败
|
||||
install.installer.choose=选择%s版本
|
||||
install.installer.failed=未能成功安装%s.
|
||||
install.installer.choose=选择 %s 版本
|
||||
install.installer.forge=Forge
|
||||
install.installer.game=游戏
|
||||
install.installer.install=安装%s
|
||||
install.installer.install=安装 %s
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=不安装%s
|
||||
install.installer.not_installed=暂不安装 %s,可以点击此处安装
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s版本
|
||||
install.installer.version=%s 版本
|
||||
install.modpack=安装整合包
|
||||
install.new_game=安装新游戏版本
|
||||
install.new_game.already_exists=此版本已经存在,请换一个名字
|
||||
install.new_game.current_game_version=当前游戏版本
|
||||
install.no_version=未找到要安装的对应MC版本
|
||||
install.no_version_if_intall=未找到要安装的对应MC版本,是否自动安装需要的MC版本?
|
||||
install.release_time=发布时间
|
||||
install.select=请选择安装方式
|
||||
install.success=安装成功
|
||||
install.time=时间
|
||||
install.type=类型
|
||||
|
||||
lang=简体中文
|
||||
lang.default=跟随系统语言
|
||||
|
||||
launch.advice.different_platform=您的系统是64位的但是Java是32位的,推荐您安装64位Java.
|
||||
launch.advice.java9=不高于1.13的Minecraft不支持Java 9或更高版本,请使用Java 8.
|
||||
launch.advice.newer_java=检测到您未使用Java 8及更新版本,Java 8能使游戏更流畅而且Minecraft 1.12及更新版本和很多Mod强制需要需要Java 8。
|
||||
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小%dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了32位Java的内存分配限制,所以可能无法启动游戏,请将内存调至1024MB或更小,启动器仍会尝试启动。
|
||||
launch.circular_dependency_versions=发现游戏版本循环引用,请确认您的客户端未被修改或修改导致出现此问题。
|
||||
launch.advice.different_platform=您的系统是 64 位的但是 Java 是 32 位的,推荐您安装 64 位 Java。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
|
||||
launch.advice.java9=不高于 1.13 的安装了 Mod 的 Minecraft 不支持 Java 9 或更高版本,请使用 Java 8。
|
||||
launch.advice.newer_java=检测到您未使用 Java 8 及更新版本,Java 8 能使游戏更流畅而且 Minecraft 1.12 及更新版本和很多 Mod 强制需要需要 Java 8。
|
||||
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小,启动器仍会尝试启动。
|
||||
launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建Java虚拟机,可能是Java参数有问题,可以在设置中开启无参数模式启动
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是Java路径错误。
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
launch.failed.decompressing_natives=未能解压游戏本地库。
|
||||
launch.failed.downloading_libraries=未完成游戏依赖库的下载,还要继续启动游戏吗?
|
||||
launch.failed.executable_permission=未能为启动文件添加执行权限
|
||||
launch.failed.executable_permission=未能为启动文件添加执行权限。
|
||||
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
|
||||
launch.failed.prelaunch_command=启动前执行命令执行失败。
|
||||
launch.state.dependencies=正在处理游戏依赖
|
||||
launch.state.done=启动完成
|
||||
launch.state.logging_in=登录中
|
||||
launch.state.modpack=正在下载必要文件
|
||||
launch.state.waiting_launching=等待游戏启动
|
||||
launch.unsupported_launcher_version=对不起,本启动器现在可能不能启动这个版本的Minecraft,但启动器还是会尝试启动,请尽快将此问题报告给作者。
|
||||
launch.wrong_javadir=错误的Java路径,将自动重置为默认Java路径。
|
||||
launch.wrong_javadir=错误的 Java 路径,将自动重置为默认 Java 路径。
|
||||
|
||||
launcher=启动器
|
||||
launcher.background=背景地址
|
||||
launcher.background.choose=选择背景路径
|
||||
launcher.background.default=默认(自动检索启动器同目录下的background.png/jpg及bg文件夹内的图片)
|
||||
launcher.background.default=默认(自动检索启动器同目录下的 background.png/jpg 及 bg 文件夹内的图片)
|
||||
launcher.common_directory=公共文件夹
|
||||
launcher.common_directory.choose=选择公共文件夹
|
||||
launcher.contact=联系我们
|
||||
launcher.crash=Hello Minecraft!遇到了无法处理的错误,请复制下列内容并通过mcbbs、贴吧、Github或Minecraft Forum反馈bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
launcher.exit_failed=强制退出失败,可能是Forge 1.7.10及更高版本导致的,无法解决。
|
||||
launcher.modpack=整合包作者帮助
|
||||
launcher.open_jdk=我们发现您正在使用OpenJDK,这会导致很多界面问题,我们建议您更换Oracle JDK。
|
||||
launcher.restart=本界面选项需要重启本启动器生效
|
||||
launcher.tab.about=关于
|
||||
launcher.tab.general=通用
|
||||
launcher.tab.ui=界面
|
||||
launcher.update_java=请更新您的Java
|
||||
launcher.update_launcher=检查更新
|
||||
launcher.versions_json_not_formatted=版本%s信息文件格式错误,是否重新下载?
|
||||
launcher.versions_json_not_matched=版本%s格式不规范!该版本文件夹下有json:%s,是否更名这个文件来规范格式?
|
||||
launcher.versions_json_not_matched_cannot_auto_completion=版本%s缺失必要的版本信息文件,是否删除该版本?
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并通过 MCBBS、贴吧、GitHub 或 Minecraft Forum 反馈 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
launcher.update_java=请更新您的 Java
|
||||
|
||||
login.changed_client_token=服务器回应已经修改客户端令牌
|
||||
login.choose_charactor=请选择您要使用的角色
|
||||
login.empty_username=你还未设置用户名!
|
||||
login.enter_password=请输入您的密码
|
||||
login.enter_username=请输入您的账号
|
||||
login.failed=登录失败:
|
||||
login.invalid_username=无效的用户名
|
||||
login.invalid_uuid_and_username=无效的UUID和用户名
|
||||
login.no_valid_character=无有效的角色,自行到skinme.cc登陆并创建角色
|
||||
|
||||
logwindow.search=查找
|
||||
logwindow.show_lines=显示行数
|
||||
logwindow.terminate_game=结束游戏进程
|
||||
logwindow.title=日志
|
||||
|
||||
main_page=主页
|
||||
|
||||
message.cancelled=已取消
|
||||
message.cannot_open_explorer=无法打开文件管理器:
|
||||
message.confirm=提示
|
||||
message.doing=请耐心等待
|
||||
message.downloading=正在下载...
|
||||
message.error=错误
|
||||
message.info=提示
|
||||
message.loading=加载中...
|
||||
message.success=已完成
|
||||
message.unknown=未知
|
||||
|
||||
minecraft.invalid=无效的
|
||||
minecraft.invalid_jar=无效的jar包
|
||||
minecraft.modified=(修改的!)
|
||||
minecraft.not_a_file=不是文件
|
||||
minecraft.not_found=找不到minecraft.jar
|
||||
minecraft.not_readable=minecraft.jar不可读
|
||||
minecraft.wrong_path=错误的Minecraft路径,启动器未找到设定的Minecraft路径,请检查。
|
||||
|
||||
modpack=整合包
|
||||
modpack.choose=选择要导入的游戏整合包文件,如果您希望更新整合包,请输入要更新的版本名
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持Markdown(图片请用网络图片)。
|
||||
modpack.choose=选择要安装的游戏整合包文件
|
||||
modpack.desc=描述你要制作的整合包,比如整合包注意事项和更新记录,支持 Markdown(图片请用网络图)。
|
||||
modpack.enter_name=给游戏起个你喜欢的名字
|
||||
modpack.export=导出整合包
|
||||
modpack.export_error=导出失败,可能是您的游戏文件夹格式不正确或操作文件失败
|
||||
modpack.export_finished=整合包导出完成,参见
|
||||
modpack.files.blueprints=BuildCraft蓝图
|
||||
modpack.files.config=Mod配置文件
|
||||
modpack.files.dumps=NEI调试输出
|
||||
modpack.files.liteconfig=Mod配置文件
|
||||
modpack.files.blueprints=BuildCraft 蓝图
|
||||
modpack.files.config=Mod 配置文件
|
||||
modpack.files.dumps=NEI 调试输出
|
||||
modpack.files.liteconfig=Mod 配置文件
|
||||
modpack.files.mods=Mod
|
||||
modpack.files.mods.voxelmods=VoxelMods配置,如小地图
|
||||
modpack.files.mods.voxelmods=VoxelMods 配置,如小地图
|
||||
modpack.files.options_txt=游戏设定
|
||||
modpack.files.optionsshaders_txt=光影设定
|
||||
modpack.files.resourcepacks=资源包(材质包)
|
||||
modpack.files.saves=游戏存档
|
||||
modpack.files.scripts=MineTweaker配置
|
||||
modpack.files.scripts=MineTweaker 配置
|
||||
modpack.files.servers_dat=多人游戏服务器列表
|
||||
modpack.included_launcher=整合包已包含启动器,可直接发布
|
||||
modpack.install=安装%s整合包
|
||||
modpack.installing=正在安装整合包
|
||||
modpack.introduction=支持 Curse, MultiMC, HMCL, MCBBS 整合包。
|
||||
modpack.introduction=支持 Curse、MultiMC、HMCL、MCBBS 整合包。
|
||||
modpack.invalid=无效的整合包升级文件,可能是下载时出现问题。
|
||||
modpack.mismatched_type=整合包类型不匹配,当前游戏是 %s 整合包,但是提供的整合包更新文件是 %s 整合包。
|
||||
modpack.name=整合包名称
|
||||
modpack.not_a_valid_location=不是一个有效整合包位置
|
||||
modpack.not_a_valid_name=不是一个有效的整合包名称
|
||||
modpack.not_included_launcher=整合包未包含启动器,可使用本软件的导入整合包功能导入整合包
|
||||
modpack.scan=解析整合包
|
||||
modpack.task.install=导入整合包
|
||||
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败
|
||||
modpack.task.install.error=安装失败,可能是整合包格式不正确或操作文件失败。
|
||||
modpack.task.install.will=将会安装整合包:
|
||||
modpack.type.curse=Curse
|
||||
modpack.type.curse.completion=下载Curse整合包相关文件
|
||||
modpack.type.curse.completion=下载 Curse 整合包相关文件
|
||||
modpack.type.curse.error=未能完成 Curse 整合包的下载,请多次重试或设置代理
|
||||
modpack.type.hmcl=HMCL
|
||||
modpack.type.multimc=MultiMC
|
||||
modpack.unsupported=该整合包不被支持。仅HMCL、MultiMC、Curse整合包受支持。
|
||||
modpack.unsupported=该整合包不被支持。仅 HMCL、MultiMC、Curse 整合包受支持。
|
||||
modpack.update=正在升级整合包
|
||||
modpack.wizard=导出整合包向导
|
||||
modpack.wizard.step.1=基本设置
|
||||
modpack.wizard.step.1.title=设置整合包的主要信息
|
||||
modpack.wizard.step.2=文件选择
|
||||
modpack.wizard.step.2.title=选中你想加到整合包中的文件(夹)
|
||||
modpack.wizard.step.2.title=选中你想加到整合包中的文件或文件夹
|
||||
modpack.wizard.step.3=整合包描述
|
||||
modpack.wizard.step.3.title=描述你要制作的整合包
|
||||
modpack.wizard.step.initialization.exported_version=要导出的游戏版本
|
||||
modpack.wizard.step.initialization.include_launcher=包含启动器
|
||||
modpack.wizard.step.initialization.save=选择要导出到的游戏整合包位置
|
||||
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的Minecraft是正式版而非快照版,\n而且不应当将不允许非官方途径传播的Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
|
||||
modpack.wizard.step.initialization.warning=在制作整合包前,请您确认您选择的版本可以正常启动,\n并保证您的 Minecraft 是正式版而非快照版,\n而且不应当将不允许非官方途径传播的 Mod、材质包等纳入整合包。\n整合包会保存您目前的下载源设置
|
||||
|
||||
mods=模组管理
|
||||
mods.add=添加模组
|
||||
mods.add.failed=添加模组%s失败。
|
||||
mods.add.success=成功添加模组%s。
|
||||
mods.add.failed=添加模组 %s 失败。
|
||||
mods.add.success=成功添加模组 %s。
|
||||
mods.choose_mod=选择模组
|
||||
mods.default_information=安装Mod前你需要确保已安装Forge或LiteLoader!\n您可以从资源管理器拖动mod文件到列表中来添加mod,同时使用删除键可快速删除选中mod\n点掉mod前面的勾可禁用mod,不会加载;选择mod可以获取mod信息
|
||||
mods.remove=删除
|
||||
|
||||
operation.confirm_stop=真的要终止操作吗?
|
||||
operation.stopped=操作被强行终止
|
||||
|
||||
profile=配置
|
||||
profile.default=当前目录
|
||||
profile.home=主文件夹
|
||||
profile.instance_directory=游戏路径
|
||||
profile.instance_directory.choose=选择游戏路径
|
||||
profile.new=新建配置
|
||||
profile.new_name=新配置名:
|
||||
profile.remove=真的要删除配置%s吗?
|
||||
profile.title=游戏目录
|
||||
profile.selected=已选中
|
||||
|
||||
@@ -325,35 +249,33 @@ settings=普通设置
|
||||
|
||||
settings.advanced=高级设置
|
||||
settings.advanced.dont_check_game_completeness=不检查游戏完整性
|
||||
settings.advanced.game_dir.default=默认(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除assets,libraries)
|
||||
settings.advanced.game_dir.default=默认(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_args_default=启动器默认添加的参数(请不要重复添加):-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:MaxPermSize=???m -Xmx???m -Dfml.ignoreInvalidMinecraftCertificates=true -Dfml.ignorePatchDiscrepancies=true
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,MB)
|
||||
settings.advanced.jvm_args=Java虚拟机参数(不必填写)
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,MB)
|
||||
settings.advanced.jvm_args=Java 虚拟机参数(不必填写)
|
||||
settings.advanced.launcher_visibility.close=游戏启动后结束启动器
|
||||
settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器
|
||||
settings.advanced.launcher_visibility.hide_reopen=隐藏启动器并在游戏结束后重新打开
|
||||
settings.advanced.launcher_visibility.keep=保持启动器可见
|
||||
settings.advanced.launcher_visible=启动器可见性
|
||||
settings.advanced.minecraft_arguments=Minecraft额外参数(不必填写)
|
||||
settings.advanced.minecraft_arguments=Minecraft 额外参数(不必填写)
|
||||
settings.advanced.no_common=不使用公共路径
|
||||
settings.advanced.no_jvm_args=不添加默认的JVM参数(使用Java9时必勾)
|
||||
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
|
||||
settings.advanced.server_ip=直入服务器ip地址(不必填写,启动游戏后直接进入对应服务器)
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填写,如optirun)
|
||||
settings.advanced.no_jvm_args=不添加默认的 JVM 参数(使用 Java 9 时必勾)
|
||||
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
|
||||
settings.advanced.server_ip=直入服务器 IP 地址(不必填写,启动游戏后直接进入对应服务器)
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填写,如 optirun)
|
||||
|
||||
settings.cannot_remove_default_config=不能删除默认配置
|
||||
settings.choose_gamedir=选择游戏路径
|
||||
settings.custom=自定义
|
||||
settings.default=默认
|
||||
settings.failed_load=设置文件加载失败,可能是升级了启动器或被人工修改造成错误,是否清除?
|
||||
|
||||
settings.game=游戏设置
|
||||
settings.game.dimension=游戏窗口分辨率
|
||||
settings.game.exploration=浏览
|
||||
settings.game.fullscreen=全屏
|
||||
settings.game.java_directory=Java路径
|
||||
settings.game.java_directory.choose=选择Java路径
|
||||
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=游戏图标
|
||||
@@ -375,18 +297,13 @@ settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=账户
|
||||
settings.launcher.theme=主题
|
||||
|
||||
settings.max_memory=最大内存/MB
|
||||
settings.max_memory=最大内存(MB)
|
||||
settings.physical_memory=物理内存大小
|
||||
settings.run_directory=运行路径(版本隔离)
|
||||
settings.show_log=查看日志
|
||||
settings.tabs.assets_downloads=资源下载
|
||||
settings.tabs.game_download=游戏下载
|
||||
settings.tabs.installers=自动安装
|
||||
settings.test_game=测试游戏
|
||||
settings.type.global=全局版本设置(使用该设置的版本共用一套设定)
|
||||
settings.type=版本设置类型
|
||||
settings.type.special=单独版本设置(不会影响到其他版本的设定)
|
||||
settings.update_version=更新版本文件
|
||||
|
||||
update=启动器更新
|
||||
update.failed=检查更新失败
|
||||
@@ -394,7 +311,6 @@ update.found=发现更新
|
||||
update.newest_version=最新版本为:%s
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器,网址已经复制到剪贴板了,您可以手动粘贴网址打开页面
|
||||
update.should_open_link=是否更新?
|
||||
update.tooltip=更新
|
||||
|
||||
version.cannot_read=读取游戏版本失败,无法进行自动安装
|
||||
@@ -406,9 +322,8 @@ version.launch=启动游戏
|
||||
version.launch_script=生成启动脚本
|
||||
version.launch_script.failed=生成启动脚本失败
|
||||
version.launch_script.save=保存启动脚本
|
||||
version.launch_script.success=启动脚本已生成完毕: %s.
|
||||
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
|
||||
version.manage.redownload_json=重新下载版本配置(minecraft.json)
|
||||
version.launch_script.success=启动脚本已生成完毕:%s
|
||||
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
|
||||
version.manage.remove=删除该版本
|
||||
version.manage.remove.confirm=真的要删除版本 %s 吗?
|
||||
version.manage.remove_libraries=删除所有库文件
|
||||
@@ -418,10 +333,6 @@ version.settings=游戏设置
|
||||
version.update=更新
|
||||
|
||||
wizard.prev=< 上一步
|
||||
wizard.close=关闭
|
||||
wizard.failed=失败
|
||||
wizard.finish=完成
|
||||
wizard.help=帮助
|
||||
wizard.next=下一步 >
|
||||
wizard.steps=步骤
|
||||
wizard.summary=概要
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 653 KiB |
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.auth;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -29,39 +30,37 @@ import java.util.UUID;
|
||||
public abstract class Account {
|
||||
|
||||
/**
|
||||
* @return the account name (mostly email)
|
||||
* @return the name of the account who owns the character
|
||||
*/
|
||||
public abstract String getUsername();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the character name
|
||||
*/
|
||||
public abstract String getCharacter();
|
||||
|
||||
/**
|
||||
* @return the UUID
|
||||
* @return the character UUID
|
||||
*/
|
||||
public abstract UUID getUUID();
|
||||
|
||||
/**
|
||||
* log in.
|
||||
* @return the specific player's info.
|
||||
* @throws AuthenticationException if server error occurred.
|
||||
* Login with stored credentials.
|
||||
*
|
||||
* @throws CredentialExpiredException when the stored credentials has expired, in which case a password login will be performed
|
||||
*/
|
||||
public abstract AuthInfo logIn() throws AuthenticationException;
|
||||
public abstract AuthInfo logIn() throws CredentialExpiredException, AuthenticationException;
|
||||
|
||||
/**
|
||||
* Login with specified password.
|
||||
*/
|
||||
public abstract AuthInfo logInWithPassword(String password) throws AuthenticationException;
|
||||
|
||||
public abstract boolean canPlayOffline();
|
||||
|
||||
/**
|
||||
* Play offline.
|
||||
* @return the specific offline player's info.
|
||||
*/
|
||||
public abstract AuthInfo playOffline();
|
||||
|
||||
public abstract void logOut();
|
||||
public abstract Optional<AuthInfo> playOffline();
|
||||
|
||||
public abstract Map<Object, Object> toStorage();
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ package org.jackhuang.hmcl.auth;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -28,29 +30,19 @@ import org.jackhuang.hmcl.util.Immutable;
|
||||
public final class AuthInfo {
|
||||
|
||||
private final String username;
|
||||
private final String userId;
|
||||
private final UUID uuid;
|
||||
private final String accessToken;
|
||||
private final UserType userType;
|
||||
private final String userProperties;
|
||||
private final Arguments arguments;
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken) {
|
||||
this(username, userId, accessToken, UserType.LEGACY);
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties) {
|
||||
this(username, uuid, accessToken, userProperties, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken, UserType userType) {
|
||||
this(username, userId, accessToken, userType, "{}");
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties) {
|
||||
this(username, userId, accessToken, userType, userProperties, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties, Arguments arguments) {
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties, Arguments arguments) {
|
||||
this.username = username;
|
||||
this.userId = userId;
|
||||
this.uuid = uuid;
|
||||
this.accessToken = accessToken;
|
||||
this.userType = userType;
|
||||
this.userProperties = userProperties;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
@@ -59,18 +51,14 @@ public final class AuthInfo {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public UserType getUserType() {
|
||||
return userType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties of this user.
|
||||
* Don't know the difference between user properties and user property map.
|
||||
@@ -81,11 +69,14 @@ public final class AuthInfo {
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if no argument is specified
|
||||
*/
|
||||
public Arguments getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public AuthInfo setArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, userId, accessToken, userType, userProperties, arguments);
|
||||
public AuthInfo withArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, uuid, accessToken, userProperties, arguments);
|
||||
}
|
||||
}
|
||||
+20
-2
@@ -18,7 +18,25 @@
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
/**
|
||||
* throws if wrong password.
|
||||
* Thrown when the stored credentials has expired.
|
||||
* This exception indicates that a password login should be performed.
|
||||
*
|
||||
* @author yushijinhun
|
||||
* @see Account#logIn()
|
||||
*/
|
||||
public class InvalidPasswordException extends AuthenticationException {
|
||||
public class CredentialExpiredException extends AuthenticationException {
|
||||
|
||||
public CredentialExpiredException() {}
|
||||
|
||||
public CredentialExpiredException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public CredentialExpiredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CredentialExpiredException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
/**
|
||||
* throws if wrong password or logging fails for too many times.
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class InvalidCredentialsException extends AuthenticationException {
|
||||
}
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
public class ServerResponseMalformedException extends AuthenticationException {
|
||||
|
||||
@@ -20,23 +20,24 @@ package org.jackhuang.hmcl.auth;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Select character by name.
|
||||
*/
|
||||
public class SpecificCharacterSelector implements CharacterSelector {
|
||||
private final String id;
|
||||
private UUID uuid;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param id character's name.
|
||||
* @param uuid character's uuid.
|
||||
*/
|
||||
public SpecificCharacterSelector(String id) {
|
||||
this.id = id;
|
||||
public SpecificCharacterSelector(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException {
|
||||
return names.stream().filter(profile -> profile.getName().equals(id)).findAny().orElseThrow(() -> new NoSelectedCharacterException(account));
|
||||
return names.stream().filter(profile -> profile.getId().equals(uuid)).findAny().orElseThrow(() -> new NoSelectedCharacterException(account));
|
||||
}
|
||||
}
|
||||
@@ -1,49 +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;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public enum UserType {
|
||||
LEGACY,
|
||||
MOJANG;
|
||||
|
||||
public static UserType fromName(String name) {
|
||||
return BY_NAME.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public static UserType fromLegacy(boolean isLegacy) {
|
||||
return isLegacy ? LEGACY : MOJANG;
|
||||
}
|
||||
|
||||
static {
|
||||
HashMap<String, UserType> byName = new HashMap<>();
|
||||
for (UserType type : values())
|
||||
byName.put(type.name().toLowerCase(), type);
|
||||
BY_NAME = Collections.unmodifiableMap(byName);
|
||||
}
|
||||
|
||||
public static final Map<String, UserType> BY_NAME;
|
||||
|
||||
}
|
||||
+50
-28
@@ -25,24 +25,30 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
private final String serverBaseURL;
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||
private AuthlibInjectorServer server;
|
||||
private ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader;
|
||||
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, String clientToken, String character, YggdrasilSession session) {
|
||||
super(service, username, clientToken, character, session);
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, AuthlibInjectorServer server, ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader, String username, UUID characterUUID, YggdrasilSession session) {
|
||||
super(service, username, characterUUID, session);
|
||||
|
||||
this.injectorJarPath = injectorJarPath;
|
||||
this.serverBaseURL = serverBaseURL;
|
||||
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,37 +61,53 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
return inject(() -> super.logInWithPassword(password, selector));
|
||||
}
|
||||
|
||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> supplier) throws AuthenticationException {
|
||||
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
|
||||
GetTask getTask = new GetTask(NetworkUtils.toURL(serverBaseURL));
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
|
||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {
|
||||
// Pre-fetch metadata
|
||||
GetTask metadataFetchTask = new GetTask(NetworkUtils.toURL(server.getUrl()));
|
||||
Thread metadataFetchThread = Lang.thread(() -> {
|
||||
try {
|
||||
metadataFetchTask.run();
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Failed to pre-fetch Yggdrasil metadata", e);
|
||||
}
|
||||
}, "Yggdrasil metadata fetch thread");
|
||||
|
||||
AuthInfo info = supplier.get();
|
||||
// Update authlib-injector
|
||||
AuthlibInjectorArtifactInfo artifact;
|
||||
try {
|
||||
thread.join();
|
||||
|
||||
String arg = "-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL;
|
||||
Arguments arguments = Arguments.addJVMArguments(null, arg);
|
||||
|
||||
if (flag.get())
|
||||
arguments = Arguments.addJVMArguments(arguments, "-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), Charsets.UTF_8));
|
||||
|
||||
return info.setArguments(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("Unable to get authlib injector jar path", e);
|
||||
artifact = authlibInjectorDownloader.get();
|
||||
} catch (IOException e) {
|
||||
throw new AuthenticationException("Failed to download authlib-injector", e);
|
||||
}
|
||||
|
||||
// Perform authentication
|
||||
AuthInfo info = loginAction.get();
|
||||
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl());
|
||||
|
||||
// Wait for metadata to be fetched
|
||||
try {
|
||||
metadataFetchThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
Optional<String> metadata = Optional.ofNullable(metadataFetchTask.getResult());
|
||||
if (metadata.isPresent()) {
|
||||
arguments = arguments.addJVMArguments(
|
||||
"-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(metadata.get().getBytes(UTF_8)));
|
||||
}
|
||||
|
||||
return info.withArguments(arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> toStorage() {
|
||||
Map<Object, Object> map = super.toStorage();
|
||||
map.put("serverBaseURL", serverBaseURL);
|
||||
map.put("serverBaseURL", server.getUrl());
|
||||
return map;
|
||||
}
|
||||
|
||||
public String getServerBaseURL() {
|
||||
return serverBaseURL;
|
||||
public AuthlibInjectorServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 163 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user