Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20a7039f59 | ||
|
|
4ad172dda4 | ||
|
|
adf6c19996 | ||
|
|
24ec0adacf | ||
|
|
83bc6748a3 | ||
|
|
bfed651263 | ||
|
|
8bd4f2b5b5 | ||
|
|
3d7bc0b593 | ||
|
|
473c6f2b3c | ||
|
|
484ce6c895 | ||
|
|
eeccdbca72 | ||
|
|
552238676c |
No files matched your search
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class Main extends Application {
|
||||
@@ -89,6 +90,10 @@ public final class Main extends Application {
|
||||
|
||||
Logging.LOG.info("Shutting down executor services.");
|
||||
Schedulers.shutdown();
|
||||
|
||||
Controllers.shutdown();
|
||||
|
||||
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -121,11 +126,11 @@ public final class Main extends Application {
|
||||
}
|
||||
|
||||
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 NAME = "HMCL";
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
public static final File APPDATA = getWorkingDirectory("hmcl");
|
||||
public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION));
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
|
||||
|
||||
@@ -45,7 +45,7 @@ public final class AccountHelper {
|
||||
public static final AccountHelper INSTANCE = new AccountHelper();
|
||||
private AccountHelper() {}
|
||||
|
||||
public static final File SKIN_DIR = new File(Main.APPDATA, "skins");
|
||||
public static final File SKIN_DIR = new File(Main.HMCL_DIRECTORY, "skins");
|
||||
|
||||
public static void loadSkins() {
|
||||
loadSkins(Proxy.NO_PROXY);
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -81,13 +82,9 @@ public final class HMCLModpackManager {
|
||||
*/
|
||||
public static Modpack readHMCLModpackManifest(File file) throws IOException, JsonParseException {
|
||||
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
|
||||
Modpack manifest = Constants.GSON.fromJson(manifestJson, Modpack.class);
|
||||
if (manifest == null)
|
||||
throw new JsonParseException("`modpack.json` not found. " + file + " is not a valid HMCL modpack.");
|
||||
Modpack manifest = Lang.requireJsonNonNull(Constants.GSON.fromJson(manifestJson, Modpack.class));
|
||||
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
|
||||
Version game = Constants.GSON.fromJson(gameJson, Version.class);
|
||||
if (game == null)
|
||||
throw new JsonParseException("`minecraft/pack.json` not found. " + file + " iot a valid HMCL modpack.");
|
||||
Version game = Lang.requireJsonNonNull(Constants.GSON.fromJson(gameJson, Version.class));
|
||||
if (game.getJar() == null)
|
||||
if (StringUtils.isBlank(manifest.getVersion()))
|
||||
throw new JsonParseException("Cannot recognize the game version of modpack " + file + ".");
|
||||
|
||||
@@ -73,6 +73,7 @@ public final class LauncherHelper {
|
||||
Version version = repository.getVersion(selectedVersion);
|
||||
Account account = Settings.INSTANCE.getSelectedAccount();
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
executor = Task.of(Schedulers.javafx(), () -> Controllers.dialog(launchingStepsPane))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)))
|
||||
@@ -87,12 +88,14 @@ public final class LauncherHelper {
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
|
||||
.then(Task.of(Main.i18n("account.methods"), variables -> {
|
||||
try {
|
||||
variables.set("account", account.logIn(new SpecificCharacterSelector(Accounts.getCurrentCharacter(account)), Settings.INSTANCE.getProxy()));
|
||||
} catch (ServerDisconnectException e) {
|
||||
if (account.canPlayOffline())
|
||||
variables.set("account", account.playOffline());
|
||||
else
|
||||
throw e;
|
||||
try {
|
||||
variables.set("account", account.logIn(new SpecificCharacterSelector(Accounts.getCurrentCharacter(account)), Settings.INSTANCE.getProxy()));
|
||||
} catch (ServerDisconnectException e) {
|
||||
if (account.canPlayOffline())
|
||||
variables.set("account", account.playOffline());
|
||||
else
|
||||
throw e;
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
variables.set("account", DialogController.logIn(account));
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(launchingStepsPane));
|
||||
@@ -101,7 +104,7 @@ public final class LauncherHelper {
|
||||
.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)
|
||||
repository, selectedVersion, variables.get("account"), setting.toLaunchOptions(profile.getGameDir()), new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent())
|
||||
));
|
||||
}))
|
||||
.then(variables -> {
|
||||
@@ -140,21 +143,26 @@ public final class LauncherHelper {
|
||||
@Override
|
||||
public void onFinished(Task task) {
|
||||
finished.incrementAndGet();
|
||||
Platform.runLater(() -> {
|
||||
launchingStepsPane.setProgress(1.0 * finished.get() / executor.getRunningTasks());
|
||||
});
|
||||
int runningTasks = executor.getRunningTasks();
|
||||
Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
Platform.runLater(() -> {
|
||||
if (executor.getLastException() != null)
|
||||
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
|
||||
scriptFile == null ? Main.i18n("launch.failed") : Main.i18n("version.launch_script.failed"),
|
||||
MessageBox.ERROR_MESSAGE, Controllers::closeDialog);
|
||||
else
|
||||
Controllers.closeDialog();
|
||||
});
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
if (success) {
|
||||
Controllers.closeDialog();
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
if (executor.getLastException() != null)
|
||||
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
|
||||
scriptFile == null ? Main.i18n("launch.failed") : Main.i18n("version.launch_script.failed"),
|
||||
MessageBox.ERROR_MESSAGE, Controllers::closeDialog);
|
||||
else
|
||||
Controllers.closeDialog();
|
||||
});
|
||||
}
|
||||
|
||||
LauncherHelper.this.executor = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -214,8 +222,6 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE)
|
||||
Controllers.closeDialog();
|
||||
|
||||
launchingStepsPane.setTitle(state.getLocalizedMessage());
|
||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
@@ -284,11 +290,13 @@ public final class LauncherHelper {
|
||||
private ManagedProcess process;
|
||||
private boolean lwjgl;
|
||||
private LogWindow logWindow;
|
||||
private final boolean detectWindow;
|
||||
private final LinkedList<Pair<String, Log4jLevel>> logs;
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
public HMCLProcessListener(AuthInfo authInfo, VersionSetting setting) {
|
||||
public HMCLProcessListener(AuthInfo authInfo, VersionSetting setting, boolean detectWindow) {
|
||||
this.setting = setting;
|
||||
this.detectWindow = detectWindow;
|
||||
|
||||
if (authInfo == null)
|
||||
forbiddenTokens = Collections.emptyMap();
|
||||
@@ -339,8 +347,7 @@ public final class LauncherHelper {
|
||||
Platform.runLater(() -> logWindow.logLine(log, level));
|
||||
}
|
||||
|
||||
|
||||
if (!lwjgl && log.contains("LWJGL Version: ")) {
|
||||
if (!lwjgl && log.contains("LWJGL Version: ") || !detectWindow) {
|
||||
lwjgl = true;
|
||||
switch (visibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
|
||||
@@ -17,15 +17,20 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
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.Optional;
|
||||
|
||||
@@ -37,14 +42,19 @@ public final class Accounts {
|
||||
|
||||
public static final String OFFLINE_ACCOUNT_KEY = "offline";
|
||||
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, YggdrasilAccountFactory.INSTANCE)
|
||||
new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory()),
|
||||
new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||
);
|
||||
|
||||
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;
|
||||
else if (account instanceof YggdrasilAccount) return YGGDRASIL_ACCOUNT_KEY;
|
||||
else return YGGDRASIL_ACCOUNT_KEY;
|
||||
}
|
||||
@@ -75,4 +85,34 @@ public final class Accounts {
|
||||
static String getAccountId(String username, String character) {
|
||||
return username + ":" + character;
|
||||
}
|
||||
|
||||
private static String downloadAuthlibInjector() throws Exception {
|
||||
AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo();
|
||||
File jar = new File(Main.HMCL_DIRECTORY, "authlib-injector.jar");
|
||||
File local = new File(Main.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();
|
||||
}
|
||||
|
||||
public static String getAuthlibInjectorServerName(String serverIp) {
|
||||
if (AUTHLIB_INJECTOR_SERVER_NAMES.containsKey(serverIp))
|
||||
return AUTHLIB_INJECTOR_SERVER_NAMES.get(serverIp);
|
||||
else {
|
||||
try {
|
||||
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();
|
||||
} catch (JsonParseException | IOException | NullPointerException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,9 @@ import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.*;
|
||||
|
||||
public final class Config {
|
||||
final class Config {
|
||||
|
||||
@SerializedName("last")
|
||||
private String selectedProfile = "";
|
||||
@@ -40,6 +37,12 @@ public final class Config {
|
||||
@SerializedName("commonpath")
|
||||
private String commonDirectory = Main.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||
|
||||
@SerializedName("hasProxy")
|
||||
private boolean hasProxy = false;
|
||||
|
||||
@SerializedName("hasProxyAuth")
|
||||
private boolean hasProxyAuth = false;
|
||||
|
||||
@SerializedName("proxyType")
|
||||
private int proxyType = 0;
|
||||
|
||||
@@ -85,6 +88,9 @@ public final class Config {
|
||||
@SerializedName("logLines")
|
||||
private int logLines = 100;
|
||||
|
||||
@SerializedName("authlibInjectorServerURLs")
|
||||
private Set<String> authlibInjectorServerURLs = new HashSet<>();
|
||||
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
@@ -121,6 +127,24 @@ public final class Config {
|
||||
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;
|
||||
}
|
||||
@@ -254,4 +278,13 @@ public final class Config {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.Main;
|
||||
|
||||
public final class Profiles {
|
||||
private Profiles() {
|
||||
}
|
||||
|
||||
public static String getProfileDisplayName(Profile profile) {
|
||||
switch (profile.getName()) {
|
||||
case Settings.DEFAULT_PROFILE:
|
||||
return Main.i18n("profile.default");
|
||||
case Settings.HOME_PROFILE:
|
||||
return Main.i18n("profile.home");
|
||||
default:
|
||||
return profile.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorBuildInfo;
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
||||
@@ -39,10 +41,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
|
||||
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.net.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -89,10 +88,12 @@ public class Settings {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
save();
|
||||
|
||||
checkAuthlibInjectorServerURLs();
|
||||
checkAuthlibInjectorAccounts();
|
||||
checkProfileMap();
|
||||
|
||||
save();
|
||||
|
||||
for (Map.Entry<String, Profile> entry2 : getProfileMap().entrySet()) {
|
||||
entry2.getValue().setName(entry2.getKey());
|
||||
entry2.getValue().nameProperty().setChangedListener(this::profileNameChanged);
|
||||
@@ -124,11 +125,6 @@ public class Settings {
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
|
||||
}
|
||||
else {
|
||||
Logging.LOG.config("No settings file here, may be first loading.");
|
||||
if (!c.getConfigurations().containsKey(HOME_PROFILE))
|
||||
c.getConfigurations().put(HOME_PROFILE, new Profile(HOME_PROFILE, Main.MINECRAFT_DIRECTORY));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -229,22 +225,27 @@ public class Settings {
|
||||
SETTINGS.setProxyPass(proxyPass);
|
||||
}
|
||||
|
||||
public boolean hasProxy() { return SETTINGS.isHasProxy(); }
|
||||
|
||||
public void setHasProxy(boolean hasProxy) { SETTINGS.setHasProxy(hasProxy); }
|
||||
|
||||
public boolean hasProxyAuth() { return SETTINGS.isHasProxyAuth(); }
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) { SETTINGS.setHasProxyAuth(hasProxyAuth); }
|
||||
|
||||
private void loadProxy() {
|
||||
String host = getProxyHost();
|
||||
Integer port = Lang.toIntOrNull(getProxyPort());
|
||||
if (StringUtils.isBlank(host) || port == null)
|
||||
if (!hasProxy() || StringUtils.isBlank(host) || port == null || getProxyType() == Proxy.Type.DIRECT)
|
||||
proxy = Proxy.NO_PROXY;
|
||||
else {
|
||||
System.setProperty("http.proxyHost", getProxyHost());
|
||||
System.setProperty("http.proxyPort", getProxyPort());
|
||||
if (getProxyType() == Proxy.Type.DIRECT)
|
||||
proxy = Proxy.NO_PROXY;
|
||||
else
|
||||
proxy = new Proxy(proxyType, new InetSocketAddress(host, port));
|
||||
proxy = new Proxy(proxyType, new InetSocketAddress(host, port));
|
||||
|
||||
String user = getProxyUser();
|
||||
String pass = getProxyPass();
|
||||
if (StringUtils.isNotBlank(user) && StringUtils.isNotBlank(pass)) {
|
||||
if (hasProxyAuth() && StringUtils.isNotBlank(user) && StringUtils.isNotBlank(pass)) {
|
||||
System.setProperty("http.proxyUser", user);
|
||||
System.setProperty("http.proxyPassword", pass);
|
||||
|
||||
@@ -275,6 +276,41 @@ public class Settings {
|
||||
SETTINGS.setLogLines(logLines);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
switch (SETTINGS.getDownloadType()) {
|
||||
case 0:
|
||||
@@ -463,7 +499,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public boolean hasProfile(String name) {
|
||||
return getProfileMap().containsKey(Lang.nonNull(name, DEFAULT_PROFILE));
|
||||
return getProfileMap().containsKey(name);
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
@@ -481,6 +517,8 @@ public class Settings {
|
||||
getProfileMap().put(ver.getName(), ver);
|
||||
|
||||
ver.nameProperty().setChangedListener(this::profileNameChanged);
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
public void deleteProfile(Profile profile) {
|
||||
@@ -494,8 +532,10 @@ public class Settings {
|
||||
}
|
||||
|
||||
private void checkProfileMap() {
|
||||
if (getProfileMap().isEmpty())
|
||||
if (getProfileMap().isEmpty()) {
|
||||
getProfileMap().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE));
|
||||
getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Main.MINECRAFT_DIRECTORY));
|
||||
}
|
||||
}
|
||||
|
||||
private void onProfileChanged() {
|
||||
|
||||
@@ -36,11 +36,13 @@ import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
public final class AccountItem extends StackPane {
|
||||
|
||||
@@ -56,6 +58,7 @@ public final class AccountItem extends StackPane {
|
||||
@FXML private Label lblUser;
|
||||
@FXML private Label lblType;
|
||||
@FXML private Label lblEmail;
|
||||
@FXML private Label lblServer;
|
||||
@FXML private Label lblCurrentAccount;
|
||||
@FXML private JFXRadioButton chkSelected;
|
||||
@FXML private JFXProgressBar pgsSkin;
|
||||
@@ -67,21 +70,14 @@ public final class AccountItem extends StackPane {
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-item.fxml");
|
||||
|
||||
FXUtils.limitWidth(this, 160);
|
||||
FXUtils.limitHeight(this, 156);
|
||||
|
||||
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0));
|
||||
|
||||
chkSelected.setToggleGroup(toggleGroup);
|
||||
btnDelete.setGraphic(SVG.delete("black", 15, 15));
|
||||
btnRefresh.setGraphic(SVG.refresh("black", 15, 15));
|
||||
|
||||
// create content
|
||||
String headerColor = getDefaultColor(i % 12);
|
||||
header.setStyle("-fx-background-radius: 2 2 0 0; -fx-background-color: " + headerColor);
|
||||
|
||||
// create image view
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 32.0, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
|
||||
|
||||
chkSelected.getProperties().put("account", account);
|
||||
setSelected(Settings.INSTANCE.getSelectedAccount() == account);
|
||||
@@ -90,6 +86,13 @@ public final class AccountItem extends StackPane {
|
||||
lblType.setText(AccountsPage.accountType(account));
|
||||
lblEmail.setText(account.getUsername());
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(((AuthlibInjectorAccount) account).getServerBaseURL()))
|
||||
.subscribe(Schedulers.javafx(), variables -> {
|
||||
lblServer.setText(variables.get("serverName"));
|
||||
});
|
||||
}
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
btnRefresh.setOnMouseClicked(e -> {
|
||||
pgsSkin.setVisible(true);
|
||||
@@ -116,25 +119,6 @@ public final class AccountItem extends StackPane {
|
||||
FXUtils.limitSize(portraitView, 32, 32);
|
||||
}
|
||||
|
||||
private String getDefaultColor(int i) {
|
||||
switch (i) {
|
||||
case 0: return "#8F3F7E";
|
||||
case 1: return "#B5305F";
|
||||
case 2: return "#CE584A";
|
||||
case 3: return "#DB8D5C";
|
||||
case 4: return "#DA854E";
|
||||
case 5: return "#E9AB44";
|
||||
case 6: return "#FEE435";
|
||||
case 7: return "#99C286";
|
||||
case 8: return "#01A05E";
|
||||
case 9: return "#4A8895";
|
||||
case 10: return "#16669B";
|
||||
case 11: return "#2F65A5";
|
||||
case 12: return "#4E6A9C";
|
||||
default: return "#FFFFFF";
|
||||
}
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
+9
-15
@@ -23,11 +23,7 @@ import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.SpecificCharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.InvalidCredentialsException;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
@@ -37,8 +33,8 @@ import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class YggdrasilAccountLoginPane extends StackPane {
|
||||
private final YggdrasilAccount oldAccount;
|
||||
public class AccountLoginPane extends StackPane {
|
||||
private final Account oldAccount;
|
||||
private final Consumer<AuthInfo> success;
|
||||
private final Runnable failed;
|
||||
|
||||
@@ -49,12 +45,12 @@ public class YggdrasilAccountLoginPane extends StackPane {
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
private JFXDialog dialog;
|
||||
|
||||
public YggdrasilAccountLoginPane(YggdrasilAccount oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
this.oldAccount = oldAccount;
|
||||
this.success = success;
|
||||
this.failed = failed;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/yggdrasil-account-login.fxml");
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
|
||||
|
||||
lblUsername.setText(oldAccount.getUsername());
|
||||
txtPassword.setOnAction(e -> onAccept());
|
||||
@@ -62,14 +58,12 @@ public class YggdrasilAccountLoginPane extends StackPane {
|
||||
|
||||
@FXML
|
||||
private void onAccept() {
|
||||
String username = oldAccount.getUsername();
|
||||
String password = txtPassword.getText();
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult("login", () -> {
|
||||
try {
|
||||
Account account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password);
|
||||
return account.logIn(new SpecificCharacterSelector(Accounts.getCurrentCharacter(oldAccount)), Settings.INSTANCE.getProxy());
|
||||
return oldAccount.logInWithPassword(new SpecificCharacterSelector(Accounts.getCurrentCharacter(oldAccount)), password, Settings.INSTANCE.getProxy());
|
||||
} catch (Exception e) {
|
||||
return e;
|
||||
}
|
||||
@@ -78,10 +72,10 @@ public class YggdrasilAccountLoginPane extends StackPane {
|
||||
if (account instanceof AuthInfo) {
|
||||
success.accept(((AuthInfo) account));
|
||||
dialog.close();
|
||||
} else if (account instanceof InvalidCredentialsException) {
|
||||
lblCreationWarning.setText(Main.i18n("account.failed.wrong_password"));
|
||||
} else if (account instanceof NoSelectedCharacterException) {
|
||||
dialog.close();
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(account.getClass().toString() + ": " + ((Exception) account).getLocalizedMessage());
|
||||
lblCreationWarning.setText(AccountsPage.accountException((Exception) account));
|
||||
}
|
||||
|
||||
progressBar.setVisible(false);
|
||||
@@ -25,20 +25,17 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.util.Callback;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.*;
|
||||
import org.jackhuang.hmcl.auth.InvalidCredentialsException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
@@ -48,10 +45,14 @@ import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.ReflectionHelper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account"));
|
||||
@@ -64,7 +65,10 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXComboBox<String> cboType;
|
||||
@FXML private JFXComboBox<TwoLineListItem> cboServers;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
@FXML private Label lblAddInjectorServer;
|
||||
@FXML private Hyperlink linkAddInjectorServer;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
|
||||
@@ -73,15 +77,20 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
dialog.setDialogContainer(this);
|
||||
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
FXUtils.setValidateWhileTextChanged(txtUsername);
|
||||
FXUtils.setValidateWhileTextChanged(txtPassword);
|
||||
|
||||
cboType.getItems().setAll(Main.i18n("account.methods.offline"), Main.i18n("account.methods.yggdrasil"));
|
||||
cboType.getItems().setAll(Main.i18n("account.methods.offline"), Main.i18n("account.methods.yggdrasil"), Main.i18n("account.methods.authlib_injector"));
|
||||
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
|
||||
txtPassword.setVisible(newValue.intValue() != 0);
|
||||
cboServers.setVisible(newValue.intValue() == 2);
|
||||
linkAddInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
lblAddInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
});
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
// These two lines can eliminate black, don't know why.
|
||||
cboServers.getItems().setAll(new TwoLineListItem("", ""));
|
||||
cboServers.getSelectionModel().select(0);
|
||||
|
||||
txtPassword.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.getValidators().add(new Validator(Main.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
@@ -93,6 +102,7 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
|
||||
loadAccounts();
|
||||
loadServers();
|
||||
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty())
|
||||
addNewAccount();
|
||||
@@ -116,6 +126,17 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
}
|
||||
|
||||
public void loadServers() {
|
||||
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
|
||||
.map(serverURL -> new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL))
|
||||
.collect(Collectors.toList()))
|
||||
.subscribe(Task.of(Schedulers.javafx(), variables -> {
|
||||
cboServers.getItems().setAll(variables.<Collection<TwoLineListItem>>get("list"));
|
||||
if (!cboServers.getItems().isEmpty())
|
||||
cboServers.getSelectionModel().select(0);
|
||||
}));
|
||||
}
|
||||
|
||||
private Node buildNode(int i, Account account, ToggleGroup group) {
|
||||
AccountItem item = new AccountItem(i, account, group);
|
||||
item.setOnDeleteButtonMouseClicked(e -> {
|
||||
@@ -132,6 +153,11 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddInjecterServer() {
|
||||
Controllers.navigate(Controllers.getServersPage());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationAccept() {
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
@@ -143,8 +169,9 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
try {
|
||||
Account account;
|
||||
switch (type) {
|
||||
case 0: account = OfflineAccountFactory.INSTANCE.fromUsername(username); break;
|
||||
case 1: account = YggdrasilAccountFactory.INSTANCE.fromUsername(username, password); break;
|
||||
case 0: account = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY).fromUsername(username); break;
|
||||
case 1: account = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY).fromUsername(username, password); break;
|
||||
case 2: account = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY).fromUsername(username, password, cboServers.getSelectionModel().getSelectedItem().getSubtitle()); break;
|
||||
default: throw new Error();
|
||||
}
|
||||
|
||||
@@ -160,20 +187,10 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
Settings.INSTANCE.addAccount((Account) account);
|
||||
dialog.close();
|
||||
loadAccounts();
|
||||
} else if (account instanceof InvalidCredentialsException) {
|
||||
lblCreationWarning.setText(Main.i18n("account.failed.wrong_password"));
|
||||
} else if (account instanceof NoCharacterException) {
|
||||
lblCreationWarning.setText(Main.i18n("account.failed.no_charactor"));
|
||||
} else if (account instanceof ServerDisconnectException) {
|
||||
lblCreationWarning.setText(Main.i18n("account.failed.connect_authentication_server"));
|
||||
} else if (account instanceof InvalidTokenException) {
|
||||
lblCreationWarning.setText(Main.i18n("account.failed.invalid_token"));
|
||||
} else if (account instanceof InvalidPasswordException) {
|
||||
lblCreationWarning.setText(Main.i18n("account.failed.invalid_password"));
|
||||
} else if (account instanceof NoSelectedCharacterException) {
|
||||
dialog.close();
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(account.getClass() + ": " + ((Exception) account).getLocalizedMessage());
|
||||
lblCreationWarning.setText(accountException((Exception) account));
|
||||
}
|
||||
progressBar.setVisible(false);
|
||||
});
|
||||
@@ -197,8 +214,25 @@ public final class AccountsPage extends StackPane implements DecoratorPage {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public static String accountException(Exception account) {
|
||||
if (account instanceof InvalidCredentialsException) {
|
||||
return Main.i18n("account.failed.invalid_credentials");
|
||||
} else if (account instanceof NoCharacterException) {
|
||||
return Main.i18n("account.failed.no_charactor");
|
||||
} else if (account instanceof ServerDisconnectException) {
|
||||
return Main.i18n("account.failed.connect_authentication_server");
|
||||
} else if (account instanceof InvalidTokenException) {
|
||||
return Main.i18n("account.failed.invalid_token");
|
||||
} else if (account instanceof InvalidPasswordException) {
|
||||
return Main.i18n("account.failed.invalid_password");
|
||||
} else {
|
||||
return account.getClass() + ": " + ((Exception) account).getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return Main.i18n("account.methods.offline");
|
||||
else if (account instanceof AuthlibInjectorAccount) return Main.i18n("account.methods.authlib_injector");
|
||||
else if (account instanceof YggdrasilAccount) return Main.i18n("account.methods.yggdrasil");
|
||||
else throw new Error(Main.i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorServerInfo;
|
||||
import org.jackhuang.hmcl.mod.ModInfo;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AuthlibInjectorServerItem extends BorderPane {
|
||||
private final AuthlibInjectorServerInfo info;
|
||||
|
||||
private final Label lblServerName = new Label();
|
||||
private final Label lblServerIp = new Label();
|
||||
|
||||
public AuthlibInjectorServerItem(AuthlibInjectorServerInfo info, Consumer<AuthlibInjectorServerItem> deleteCallback) {
|
||||
this.info = info;
|
||||
|
||||
lblServerName.setStyle("-fx-font-size: 15;");
|
||||
lblServerIp.setStyle("-fx-font-size: 10;");
|
||||
|
||||
VBox center = new VBox();
|
||||
BorderPane.setAlignment(center, Pos.CENTER);
|
||||
center.getChildren().addAll(lblServerName, lblServerIp);
|
||||
setCenter(center);
|
||||
|
||||
JFXButton right = new JFXButton();
|
||||
right.setOnMouseClicked(e -> deleteCallback.accept(this));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(right, Pos.CENTER);
|
||||
right.setGraphic(SVG.close("black", 15, 15));
|
||||
setRight(right);
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblServerName.setText(info.getServerName());
|
||||
lblServerIp.setText(info.getServerIp());
|
||||
}
|
||||
|
||||
public AuthlibInjectorServerInfo getInfo() {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.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 java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account.injector.server"));
|
||||
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private StackPane addServerContainer;
|
||||
@FXML private Label lblServerIp;
|
||||
@FXML private Label lblServerName;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@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 TransitionHandler transitionHandler;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
FXUtils.smoothScrolling(scrollPane);
|
||||
transitionHandler = new TransitionHandler(addServerContainer);
|
||||
|
||||
getChildren().remove(dialog);
|
||||
dialog.setDialogContainer(this);
|
||||
|
||||
txtServerIp.textProperty().addListener((a, b, newValue) -> {
|
||||
btnAddNext.setDisable(!txtServerIp.validate());
|
||||
});
|
||||
|
||||
loading();
|
||||
}
|
||||
|
||||
private void removeServer(AuthlibInjectorServerItem item) {
|
||||
Settings.INSTANCE.removeAuthlibInjectorServerURL(item.getInfo().getServerIp());
|
||||
loading();
|
||||
}
|
||||
|
||||
private void loading() {
|
||||
getChildren().remove(contentPane);
|
||||
spinner.setVisible(true);
|
||||
|
||||
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
|
||||
.map(serverURL -> new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer))
|
||||
.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() {
|
||||
progressBar.setVisible(true);
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.ofResult("serverName", () -> Objects.requireNonNull(Accounts.getAuthlibInjectorServerName(txtServerIp.getText())))
|
||||
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
progressBar.setVisible(false);
|
||||
addServerPane.setDisable(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
lblServerName.setText(variables.get("serverName"));
|
||||
lblServerIp.setText(txtServerIp.getText());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,10 @@ public final class Controllers {
|
||||
|
||||
private static Scene scene;
|
||||
private static Stage stage;
|
||||
private static MainPage mainPage = new MainPage();
|
||||
private static MainPage mainPage = null;
|
||||
private static SettingsPage settingsPage = null;
|
||||
private static VersionPage versionPage = null;
|
||||
private static AuthlibInjectorServersPage serversPage = null;
|
||||
private static LeftPaneController leftPaneController;
|
||||
private static Decorator decorator;
|
||||
|
||||
@@ -67,11 +68,21 @@ public final class Controllers {
|
||||
return versionPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static AuthlibInjectorServersPage getServersPage() {
|
||||
if (serversPage == null)
|
||||
serversPage = new AuthlibInjectorServersPage();
|
||||
return serversPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static Decorator getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
public static MainPage getMainPage() {
|
||||
if (mainPage == null)
|
||||
mainPage = new MainPage();
|
||||
return mainPage;
|
||||
}
|
||||
|
||||
@@ -82,7 +93,7 @@ public final class Controllers {
|
||||
public static void initialize(Stage stage) {
|
||||
Controllers.stage = stage;
|
||||
|
||||
decorator = new Decorator(stage, mainPage, Main.TITLE, false, true);
|
||||
decorator = new Decorator(stage, getMainPage(), Main.TITLE, false, true);
|
||||
decorator.showPage(null);
|
||||
leftPaneController = new LeftPaneController(decorator.getLeftPane());
|
||||
|
||||
@@ -159,4 +170,14 @@ public final class Controllers {
|
||||
public static void showUpdate() {
|
||||
getDecorator().showUpdate();
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
mainPage = null;
|
||||
settingsPage = null;
|
||||
versionPage = null;
|
||||
serversPage = null;
|
||||
decorator = null;
|
||||
stage = null;
|
||||
scene = null;
|
||||
}
|
||||
}
|
||||
@@ -180,9 +180,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
||||
|
||||
FXUtils.setOverflowHidden((Pane) lookup("#contentPlaceHolderRoot"));
|
||||
FXUtils.setOverflowHidden(drawerWrapper);
|
||||
|
||||
loadBackground();
|
||||
}
|
||||
|
||||
@@ -487,7 +484,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
if (content instanceof Region) {
|
||||
((Region) content).setMinSize(0, 0);
|
||||
FXUtils.setOverflowHidden((Region) content);
|
||||
FXUtils.setOverflowHidden((Region) content, true);
|
||||
}
|
||||
|
||||
if (content instanceof Refreshable)
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.MultiCharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
@@ -35,7 +36,7 @@ public final class DialogController {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
JFXUtilities.runInFX(() -> {
|
||||
YggdrasilAccountLoginPane pane = new YggdrasilAccountLoginPane((YggdrasilAccount) account, it -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
Controllers.closeDialog();
|
||||
|
||||
@@ -38,6 +38,7 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
@@ -45,6 +46,7 @@ import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -92,35 +94,79 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(JFXTextField field) {
|
||||
field.textProperty().addListener(o -> field.validate());
|
||||
field.validate();
|
||||
public static <T> void addListener(Node node, String key, ObservableValue<T> value, Consumer<T> callback) {
|
||||
ChangeListener<T> listener = (a, b, newValue) -> callback.accept(newValue);
|
||||
node.getProperties().put(key, new Pair<>(callback, listener));
|
||||
value.addListener(listener);
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(JFXPasswordField field) {
|
||||
field.textProperty().addListener(o -> field.validate());
|
||||
field.validate();
|
||||
public static <T> void removeListener(Node node, String key) {
|
||||
if (node.getProperties().get(key) instanceof Pair) {
|
||||
Pair pair = (Pair) node.getProperties().get(key);
|
||||
if (pair.getValue() instanceof ObservableValue && pair.getKey() instanceof ChangeListener) {
|
||||
((ObservableValue) pair.getValue()).removeListener((ChangeListener) pair.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void setOverflowHidden(Region region) {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(region.widthProperty());
|
||||
rectangle.heightProperty().bind(region.heightProperty());
|
||||
region.setClip(rectangle);
|
||||
public static void setValidateWhileTextChanged(Node field, boolean validate) {
|
||||
if (field instanceof JFXTextField) {
|
||||
if (validate) {
|
||||
addListener(field, "FXUtils.validation", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate());
|
||||
} else {
|
||||
removeListener(field, "FXUtils.validation");
|
||||
}
|
||||
((JFXTextField) field).validate();
|
||||
} else if (field instanceof JFXPasswordField) {
|
||||
if (validate) {
|
||||
addListener(field, "FXUtils.validation", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate());
|
||||
} else {
|
||||
removeListener(field, "FXUtils.validation");
|
||||
}
|
||||
((JFXPasswordField) field).validate();
|
||||
} else
|
||||
throw new IllegalArgumentException("Only JFXTextField and JFXPasswordField allowed");
|
||||
}
|
||||
|
||||
public static void limitWidth(Region region, double width) {
|
||||
public static boolean getValidateWhileTextChanged(Node field) {
|
||||
return field.getProperties().containsKey("FXUtils.validation");
|
||||
}
|
||||
|
||||
public static void setOverflowHidden(Region region, boolean hidden) {
|
||||
if (hidden) {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(region.widthProperty());
|
||||
rectangle.heightProperty().bind(region.heightProperty());
|
||||
region.setClip(rectangle);
|
||||
} else {
|
||||
region.setClip(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean getOverflowHidden(Region region) {
|
||||
return region.getClip() != null;
|
||||
}
|
||||
|
||||
public static void setLimitWidth(Region region, double width) {
|
||||
region.setMaxWidth(width);
|
||||
region.setMinWidth(width);
|
||||
region.setPrefWidth(width);
|
||||
}
|
||||
|
||||
public static void limitHeight(Region region, double height) {
|
||||
public static double getLimitWidth(Region region) {
|
||||
return region.getMaxWidth();
|
||||
}
|
||||
|
||||
public static void setLimitHeight(Region region, double height) {
|
||||
region.setMaxHeight(height);
|
||||
region.setMinHeight(height);
|
||||
region.setPrefHeight(height);
|
||||
}
|
||||
|
||||
public static double getLimitHeight(Region region) {
|
||||
return region.getMaxHeight();
|
||||
}
|
||||
|
||||
public static void smoothScrolling(ScrollPane scrollPane) {
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
@@ -122,7 +123,7 @@ public final class LeftPaneController {
|
||||
private void onProfilesLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
for (Profile profile : Settings.INSTANCE.getProfiles()) {
|
||||
VersionListItem item = new VersionListItem(profile.getName());
|
||||
VersionListItem item = new VersionListItem(Profiles.getProfileDisplayName(profile));
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(() -> Controllers.getDecorator().showPage(new ProfilePage(profile)));
|
||||
ripplerContainer.setOnMouseClicked(e -> Settings.INSTANCE.setSelectedProfile(profile));
|
||||
|
||||
@@ -32,14 +32,13 @@ import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -53,11 +52,13 @@ 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.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
@@ -101,6 +102,21 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
item.setUpdate(profile.getRepository().isModpack(version));
|
||||
item.setGameVersion(game);
|
||||
item.setVersionName(version);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : profile.getRepository().getVersion(version).getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
libraries.append(Main.i18n("install.installer.forge")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)forge", "").replace(game, "").trim(), "-"), "-")).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
libraries.append(Main.i18n("install.installer.liteloader")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)liteloader", "").replace(game, "").trim(), "-"), "-")).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
libraries.append(Main.i18n("install.installer.optifine")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)optifine", "").replace(game, "").trim(), "-"), "-")).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.dialog(Main.i18n("login.empty_username"));
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.mod.ModInfo;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class ModItem extends BorderPane {
|
||||
public final class ModItem extends BorderPane {
|
||||
private final Label lblModFileName = new Label();
|
||||
private final Label lblModAuthor = new Label();
|
||||
private final JFXCheckBox chkEnabled = new JFXCheckBox();
|
||||
|
||||
@@ -25,6 +25,7 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
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;
|
||||
@@ -58,7 +59,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/profile.fxml");
|
||||
|
||||
txtProfileName.setText(Optional.ofNullable(profile).map(Profile::getName).orElse(""));
|
||||
txtProfileName.setText(Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse(""));
|
||||
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
|
||||
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
|
||||
});
|
||||
|
||||
@@ -17,18 +17,16 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
@@ -40,6 +38,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
@@ -53,12 +52,10 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private JFXTextField txtProxyUsername;
|
||||
@FXML
|
||||
private JFXTextField txtProxyPassword;
|
||||
private JFXPasswordField txtProxyPassword;
|
||||
@FXML
|
||||
private JFXTextField txtFontSize;
|
||||
@FXML
|
||||
private JFXComboBox<?> cboProxyType;
|
||||
@FXML
|
||||
private JFXComboBox<Label> cboLanguage;
|
||||
@FXML
|
||||
private JFXComboBox<?> cboDownloadSource;
|
||||
@@ -80,13 +77,24 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private MultiFileItem backgroundItem;
|
||||
@FXML
|
||||
private MultiFileItem themeItem;
|
||||
@FXML
|
||||
private JFXRadioButton chkNoProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkManualProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkProxyHttp;
|
||||
@FXML
|
||||
private JFXRadioButton chkProxySocks;
|
||||
@FXML
|
||||
private JFXCheckBox chkProxyAuthentication;
|
||||
@FXML
|
||||
private GridPane authPane;
|
||||
@FXML
|
||||
private Pane proxyPane;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/setting.fxml");
|
||||
|
||||
FXUtils.limitWidth(cboLanguage, 400);
|
||||
FXUtils.limitWidth(cboDownloadSource, 400);
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
txtProxyHost.setText(Settings.INSTANCE.getProxyHost());
|
||||
@@ -131,8 +139,37 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(Settings.INSTANCE.getLocale()));
|
||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setLocale(Locales.getLocale(newValue.intValue())));
|
||||
|
||||
cboProxyType.getSelectionModel().select(Proxies.PROXIES.indexOf(Settings.INSTANCE.getProxyType()));
|
||||
cboProxyType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyType(Proxies.getProxyType(newValue.intValue())));
|
||||
|
||||
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
|
||||
chkProxyHttp.setUserData(Proxy.Type.HTTP);
|
||||
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
|
||||
chkProxySocks.setUserData(Proxy.Type.SOCKS);
|
||||
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
|
||||
|
||||
for (Toggle toggle : proxyConfigurationGroup.getToggles())
|
||||
if (toggle.getUserData() == Settings.INSTANCE.getProxyType())
|
||||
toggle.setSelected(true);
|
||||
|
||||
ToggleGroup hasProxyGroup = new ToggleGroup();
|
||||
chkNoProxy.setToggleGroup(hasProxyGroup);
|
||||
chkManualProxy.setToggleGroup(hasProxyGroup);
|
||||
if (!Settings.INSTANCE.hasProxy())
|
||||
chkNoProxy.setSelected(true);
|
||||
else
|
||||
chkManualProxy.setSelected(true);
|
||||
proxyPane.disableProperty().bind(chkNoProxy.selectedProperty());
|
||||
|
||||
hasProxyGroup.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
Settings.INSTANCE.setHasProxy(newValue != chkNoProxy);
|
||||
});
|
||||
|
||||
proxyConfigurationGroup.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
Settings.INSTANCE.setProxyType((Proxy.Type) newValue.getUserData());
|
||||
});
|
||||
|
||||
chkProxyAuthentication.setSelected(Settings.INSTANCE.hasProxyAuth());
|
||||
chkProxyAuthentication.selectedProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setHasProxyAuth(newValue));
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
|
||||
public class TwoLineListItem extends StackPane {
|
||||
private final Label lblTitle = new Label();
|
||||
private final Label lblSubtitle = new Label();
|
||||
private final String title;
|
||||
private final String subtitle;
|
||||
|
||||
public TwoLineListItem(Pair<String, String> pair) {
|
||||
this(pair.getKey(), pair.getValue());
|
||||
}
|
||||
|
||||
public TwoLineListItem(String title, String subtitle) {
|
||||
lblTitle.setStyle("-fx-font-size: 15;");
|
||||
lblSubtitle.setStyle("-fx-font-size: 10; -fx-text-fill: gray;");
|
||||
|
||||
this.title = title;
|
||||
this.subtitle = subtitle;
|
||||
|
||||
lblTitle.setText(title);
|
||||
lblSubtitle.setText(subtitle);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.getChildren().setAll(lblTitle, lblSubtitle);
|
||||
getChildren().setAll(vbox);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getTitle();
|
||||
}
|
||||
}
|
||||
@@ -59,14 +59,14 @@ public final class VersionItem extends StackPane {
|
||||
@FXML
|
||||
private Label lblGameVersion;
|
||||
@FXML
|
||||
private Label lblLibraries;
|
||||
@FXML
|
||||
private ImageView iconView;
|
||||
|
||||
private EventHandler<? super MouseEvent> launchClickedHandler = null;
|
||||
|
||||
public VersionItem() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version-item.fxml");
|
||||
FXUtils.limitWidth(this, 160);
|
||||
FXUtils.limitHeight(this, 156);
|
||||
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
|
||||
btnSettings.setGraphic(SVG.gear("black", 15, 15));
|
||||
btnUpdate.setGraphic(SVG.update("black", 15, 15));
|
||||
@@ -100,6 +100,10 @@ public final class VersionItem extends StackPane {
|
||||
lblGameVersion.setText(gameVersion);
|
||||
}
|
||||
|
||||
public void setLibraries(String libraries) {
|
||||
lblLibraries.setText(libraries);
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
iconView.setImage(image);
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ public final class VersionListItem extends StackPane {
|
||||
lblGameVersion.setText(gameVersion);
|
||||
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
FXUtils.limitWidth(imageViewContainer, 32);
|
||||
FXUtils.limitHeight(imageViewContainer, 32);
|
||||
}
|
||||
|
||||
@FXML
|
||||
|
||||
@@ -85,28 +85,6 @@ public final class VersionSettingsController {
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
double limitWidth = 300;
|
||||
FXUtils.limitWidth(txtMaxMemory, limitWidth);
|
||||
FXUtils.limitWidth(cboLauncherVisibility, limitWidth);
|
||||
|
||||
double limitHeight = 10;
|
||||
FXUtils.limitHeight(chkNoJVMArgs, limitHeight);
|
||||
FXUtils.limitHeight(chkNoCommon, limitHeight);
|
||||
FXUtils.limitHeight(chkNoGameCheck, limitHeight);
|
||||
FXUtils.limitHeight(chkShowLogs, limitHeight);
|
||||
|
||||
NumberValidator nonnull = new NumberValidator("Must be a number.", false);
|
||||
NumberValidator nullable = new NumberValidator("Must be a number.", true);
|
||||
|
||||
txtWidth.setValidators(nonnull);
|
||||
FXUtils.setValidateWhileTextChanged(txtWidth);
|
||||
txtHeight.setValidators(nonnull);
|
||||
FXUtils.setValidateWhileTextChanged(txtHeight);
|
||||
txtMaxMemory.setValidators(nonnull);
|
||||
FXUtils.setValidateWhileTextChanged(txtMaxMemory);
|
||||
txtMetaspace.setValidators(nullable);
|
||||
FXUtils.setValidateWhileTextChanged(txtMetaspace);
|
||||
|
||||
Task.of(variables -> {
|
||||
variables.set("list", JavaVersion.getJREs().values().stream().map(javaVersion ->
|
||||
javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion)
|
||||
|
||||
@@ -103,7 +103,7 @@ public class ComponentListCell extends StackPane {
|
||||
|
||||
VBox container = new VBox();
|
||||
container.setStyle("-fx-padding: 8 0 0 0;");
|
||||
FXUtils.limitHeight(container, 0);
|
||||
FXUtils.setLimitHeight(container, 0);
|
||||
Rectangle clipRect = new Rectangle();
|
||||
clipRect.widthProperty().bind(container.widthProperty());
|
||||
clipRect.heightProperty().bind(container.heightProperty());
|
||||
|
||||
@@ -32,6 +32,7 @@ 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;
|
||||
@@ -50,6 +51,7 @@ public class MultiFileItem extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Main.i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Main.i18n("selector.choose_file"));
|
||||
private final BooleanProperty directory = new SimpleBooleanProperty(this, "directory", false);
|
||||
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||
private ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
|
||||
|
||||
private final ToggleGroup group = new ToggleGroup();
|
||||
@@ -96,7 +98,7 @@ public class MultiFileItem extends ComponentList {
|
||||
right.setSpacing(3);
|
||||
right.getChildren().addAll(txtCustom, btnSelect);
|
||||
custom.setRight(right);
|
||||
FXUtils.limitHeight(custom, 20);
|
||||
FXUtils.setLimitHeight(custom, 20);
|
||||
|
||||
pane.setStyle("-fx-padding: 0 0 10 0;");
|
||||
pane.setSpacing(8);
|
||||
@@ -109,6 +111,10 @@ 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) {
|
||||
@@ -122,7 +128,7 @@ public class MultiFileItem extends ComponentList {
|
||||
public Node createChildren(String title, String subtitle, Object userData) {
|
||||
BorderPane pane = new BorderPane();
|
||||
pane.setStyle("-fx-padding: 3;");
|
||||
FXUtils.limitHeight(pane, 20);
|
||||
FXUtils.setLimitHeight(pane, 20);
|
||||
|
||||
JFXRadioButton left = new JFXRadioButton(title);
|
||||
left.setToggleGroup(group);
|
||||
@@ -223,4 +229,16 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
@@ -28,11 +29,11 @@ public class NumberValidator extends ValidatorBase {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public NumberValidator(boolean nullable) {
|
||||
public NumberValidator(@NamedArg("nullable") boolean nullable) {
|
||||
this.nullable = nullable;
|
||||
}
|
||||
|
||||
public NumberValidator(String message, boolean nullable) {
|
||||
public NumberValidator(@NamedArg("message") String message, @NamedArg("nullable") boolean nullable) {
|
||||
super(message);
|
||||
this.nullable = nullable;
|
||||
}
|
||||
@@ -48,7 +49,7 @@ public class NumberValidator extends ValidatorBase {
|
||||
TextInputControl textField = ((TextInputControl) srcControl.get());
|
||||
|
||||
if (StringUtils.isBlank(textField.getText()))
|
||||
hasErrors.set(nullable);
|
||||
hasErrors.set(!nullable);
|
||||
else
|
||||
try {
|
||||
Integer.parseInt(textField.getText());
|
||||
|
||||
@@ -47,9 +47,6 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
public TaskExecutorDialogPane(Runnable cancel) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/task-dialog.fxml");
|
||||
|
||||
FXUtils.limitHeight(this, 200);
|
||||
FXUtils.limitWidth(this, 400);
|
||||
|
||||
setCancel(cancel);
|
||||
|
||||
btnCancel.setOnMouseClicked(e -> {
|
||||
|
||||
+16
-17
@@ -57,24 +57,23 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
}
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
TaskExecutor executor = task.executor(e -> new TaskListener() {
|
||||
TaskExecutor executor = task.executor(new TaskListener() {
|
||||
@Override
|
||||
public void onSucceed() {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(Main.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
if (e.getLastException() == null)
|
||||
return;
|
||||
String appendix = StringUtils.getStackTrace(e.getLastException());
|
||||
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Main.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
if (success) {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(Main.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
return;
|
||||
String appendix = StringUtils.getStackTrace(executor.getLastException());
|
||||
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Main.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
|
||||
}
|
||||
}
|
||||
});
|
||||
pane.setExecutor(executor);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
public class URLValidator extends ValidatorBase {
|
||||
|
||||
private final ObservableList<String> protocols = FXCollections.observableArrayList();
|
||||
|
||||
public URLValidator() {
|
||||
super();
|
||||
}
|
||||
|
||||
public URLValidator(@NamedArg("message") String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ObservableList<String> getProtocols() {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
if (srcControl.get() instanceof TextInputControl) {
|
||||
try {
|
||||
URL url = new URL(((TextInputControl) srcControl.get()).getText());
|
||||
if (protocols.isEmpty())
|
||||
hasErrors.set(false);
|
||||
else
|
||||
hasErrors.set(!protocols.contains(url.getProtocol()));
|
||||
} catch (MalformedURLException e) {
|
||||
hasErrors.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,9 +99,6 @@ public final class ModpackPage extends StackPane implements WizardPage {
|
||||
txtModpackName.setText(Main.i18n("modpack.task.install.error"));
|
||||
}
|
||||
}
|
||||
|
||||
//FXUtils.limitHeight(borderPane, 100.0);
|
||||
FXUtils.limitWidth(borderPane, 500.0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,10 +19,15 @@ package org.jackhuang.hmcl.ui.download;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -34,6 +39,12 @@ public final class VersionsPageItem extends StackPane {
|
||||
private Label lblSelfVersion;
|
||||
@FXML
|
||||
private Label lblGameVersion;
|
||||
@FXML
|
||||
private ImageView imageView;
|
||||
@FXML
|
||||
private HBox leftPane;
|
||||
@FXML
|
||||
private StackPane imageViewContainer;
|
||||
|
||||
public VersionsPageItem(RemoteVersion<?> remoteVersion) {
|
||||
this.remoteVersion = remoteVersion;
|
||||
@@ -45,17 +56,29 @@ public final class VersionsPageItem extends StackPane {
|
||||
switch (((GameRemoteVersionTag) remoteVersion.getTag()).getType()) {
|
||||
case RELEASE:
|
||||
lblGameVersion.setText(Main.i18n("version.game.release"));
|
||||
imageView.setImage(new Image("/assets/img/icon.png", 32, 32, false, true));
|
||||
break;
|
||||
case SNAPSHOT:
|
||||
lblGameVersion.setText(Main.i18n("version.game.snapshot"));
|
||||
imageView.setImage(new Image("/assets/img/command.png", 32, 32, false, true));
|
||||
break;
|
||||
default:
|
||||
lblGameVersion.setText(Main.i18n("version.game.old"));
|
||||
imageView.setImage(new Image("/assets/img/grass.png", 32, 32, false, true));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
} else if (remoteVersion.getTag() instanceof LiteLoaderRemoteVersionTag) {
|
||||
imageView.setImage(new Image("/assets/img/chicken.png", 32, 32, false, true));
|
||||
lblGameVersion.setText(remoteVersion.getGameVersion());
|
||||
} else if (remoteVersion instanceof OptiFineRemoteVersion) {
|
||||
// optifine has no icon.
|
||||
lblGameVersion.setText(remoteVersion.getGameVersion());
|
||||
} else { // forge
|
||||
imageView.setImage(new Image("/assets/img/forge.png", 32, 32, false, true));
|
||||
lblGameVersion.setText(remoteVersion.getGameVersion());
|
||||
}
|
||||
|
||||
leftPane.getChildren().remove(imageViewContainer);
|
||||
}
|
||||
|
||||
public RemoteVersion<?> getRemoteVersion() {
|
||||
|
||||
@@ -154,7 +154,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
public static class AppDataUpgraderPackGzTask extends Task {
|
||||
|
||||
public static final File BASE_FOLDER = Main.getWorkingDirectory("hmcl");
|
||||
public static final File BASE_FOLDER = Main.HMCL_DIRECTORY;
|
||||
public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json");
|
||||
|
||||
public static File getSelf(String ver) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
.root {
|
||||
-fx-font-family: "微软雅黑", "Roboto";
|
||||
-fx-font-family: "微软雅黑", "Microsoft YaHei", "Roboto";
|
||||
}
|
||||
|
||||
.disabled Label {
|
||||
|
||||
@@ -8,25 +8,21 @@
|
||||
<?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">
|
||||
type="StackPane" FXUtils.limitWidth="160" FXUtils.limitHeight="156">
|
||||
<VBox fx:id="content">
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS">
|
||||
<BorderPane>
|
||||
<top>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<JFXProgressBar fx:id="pgsSkin" visible="false" />
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<VBox style="-fx-padding: 20 20 0 20;">
|
||||
<Label fx:id="lblUser" style="-fx-font-size: 20;" wrapText="true" textAlignment="JUSTIFY" />
|
||||
<Label fx:id="lblEmail" style="-fx-font-size: 12;" />
|
||||
<Label fx:id="lblType" style="-fx-font-size: 12;" />
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS" style="-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;">
|
||||
<HBox StackPane.alignment="TOP_CENTER" alignment="CENTER_RIGHT">
|
||||
<JFXProgressBar fx:id="pgsSkin" visible="false" />
|
||||
</HBox>
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblUser" style="-fx-font-size: 15;" wrapText="true" textAlignment="JUSTIFY" />
|
||||
<Label fx:id="lblEmail" style="-fx-font-size: 11; -fx-text-fill: gray;" />
|
||||
<Label fx:id="lblServer" style="-fx-font-size: 11; -fx-text-fill: gray;" />
|
||||
<Label fx:id="lblType" style="-fx-font-size: 11; -fx-text-fill: gray;" />
|
||||
</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">
|
||||
<BorderPane>
|
||||
|
||||
File renamed without changes.
@@ -6,6 +6,8 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<?import javafx.scene.control.Hyperlink?>
|
||||
<?import java.lang.String?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
@@ -31,20 +33,28 @@
|
||||
<body>
|
||||
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints maxWidth="100" />
|
||||
<ColumnConstraints maxWidth="70" minWidth="70"/>
|
||||
<ColumnConstraints />
|
||||
<ColumnConstraints minWidth="140" />
|
||||
</columnConstraints>
|
||||
|
||||
<Label text="%account.methods" GridPane.halignment="RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
|
||||
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.columnSpan="2" />
|
||||
|
||||
<JFXTextField fx:id="txtUsername" promptText="%account.username" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="1" GridPane.columnSpan="2">
|
||||
<Label fx:id="lblAddInjectorServer" 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" />
|
||||
|
||||
<Hyperlink fx:id="linkAddInjectorServer" text="%account.injector.add" onMouseClicked="#onAddInjecterServer" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||
|
||||
<JFXTextField fx:id="txtUsername" promptText="%account.username" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="3" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%account.password" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="2" GridPane.columnSpan="2">
|
||||
<JFXPasswordField fx:id="txtPassword" promptText="%account.password" labelFloat="true" GridPane.columnIndex="0" GridPane.rowIndex="3" GridPane.columnSpan="3" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<RequiredFieldValidator message="%input.not_empty">
|
||||
</RequiredFieldValidator>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import com.jfoenix.controls.JFXSpinner?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import com.jfoenix.controls.JFXDialog?>
|
||||
<?import com.jfoenix.controls.JFXDialogLayout?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import com.jfoenix.controls.JFXTextField?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
|
||||
<?import java.lang.String?>
|
||||
<?import com.jfoenix.controls.JFXProgressBar?>
|
||||
<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;">
|
||||
|
||||
</VBox>
|
||||
</ScrollPane>
|
||||
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
|
||||
<JFXButton prefWidth="40" prefHeight="40" buttonType="RAISED" onMouseClicked="#onAdd" styleClass="jfx-button-raised-round">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/plus.fxml"/>
|
||||
</graphic>
|
||||
</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" labelFloat="true">
|
||||
<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.ok" 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 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>
|
||||
@@ -7,6 +7,7 @@
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.AdvancedListBox?>
|
||||
<?import java.lang.String?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
type="StackPane"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
@@ -18,7 +19,7 @@
|
||||
maxHeight="519"
|
||||
maxWidth="800">
|
||||
<center>
|
||||
<StackPane fx:id="drawerWrapper" styleClass="jfx-decorator-drawer">
|
||||
<StackPane fx:id="drawerWrapper" styleClass="jfx-decorator-drawer" FXUtils.overflowHidden="true">
|
||||
<JFXDialog fx:id="dialog" overlayClose="false" />
|
||||
<BorderPane>
|
||||
<left>
|
||||
@@ -47,7 +48,7 @@
|
||||
</StackPane>
|
||||
</left>
|
||||
<center>
|
||||
<StackPane fx:id="contentPlaceHolderRoot" styleClass="jfx-decorator-content-container"
|
||||
<StackPane fx:id="contentPlaceHolderRoot" styleClass="jfx-decorator-content-container" FXUtils.overflowHidden="true"
|
||||
VBox.vgrow="ALWAYS">
|
||||
<StackPane fx:id="contentPlaceHolder" styleClass="jfx-decorator-content-container">
|
||||
<!-- Node -->
|
||||
|
||||
@@ -6,13 +6,14 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
<fx:define>
|
||||
<Insets fx:id="insets" bottom="12" />
|
||||
</fx:define>
|
||||
<VBox fx:id="borderPane" alignment="CENTER">
|
||||
<VBox fx:id="borderPane" alignment="CENTER" FXUtils.limitWidth="500">
|
||||
<HBox style="-fx-padding: 0 0 16 5;"><Label text="%modpack.task.install" /></HBox>
|
||||
<ComponentList>
|
||||
<BorderPane><left><Label text="%modpack.task.install.will" /></left><right><Label fx:id="lblModpackLocation" /></right></BorderPane>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane" mouseTransparent="true">
|
||||
@@ -10,9 +13,14 @@
|
||||
</VBox>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<VBox alignment="CENTER_LEFT">
|
||||
<HBox fx:id="leftPane" alignment="CENTER_LEFT">
|
||||
<StackPane fx:id="imageViewContainer" FXUtils.limitWidth="32" FXUtils.limitHeight="32">
|
||||
<ImageView preserveRatio="true" fx:id="imageView" smooth="false">
|
||||
<Image url="/assets/img/icon.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
<Label fx:id="lblSelfVersion" style="-fx-font-size: 15;" />
|
||||
</VBox>
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<fx:include source="/assets/svg/arrow-right.fxml" />
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<bottom>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
<JFXButton fx:id="btnNext" onMouseClicked="#onNext" prefWidth="100" prefHeight="40" buttonType="RAISED"
|
||||
text="%wizard.next_>" styleClass="jfx-button-raised"/>
|
||||
text="%wizard.next" styleClass="jfx-button-raised"/>
|
||||
</HBox>
|
||||
</bottom>
|
||||
</BorderPane>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
<?import org.jackhuang.hmcl.ui.construct.FileItem?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.FontComboBox?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.MultiFileItem?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.NumberValidator?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
@@ -38,7 +40,7 @@
|
||||
<MultiFileItem fx:id="backgroundItem" title="%launcher.background" chooserTitle="%launcher.background.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" />
|
||||
|
||||
<BorderPane><left><Label text="%settings.launcher.download_source" BorderPane.alignment="CENTER_LEFT" /></left><right><JFXComboBox fx:id="cboDownloadSource">
|
||||
<BorderPane><left><Label text="%settings.launcher.download_source" BorderPane.alignment="CENTER_LEFT" /></left><right><JFXComboBox fx:id="cboDownloadSource" FXUtils.limitWidth="400">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<Label text="%download.mojang" />
|
||||
@@ -46,26 +48,69 @@
|
||||
</FXCollections>
|
||||
</items>
|
||||
</JFXComboBox></right></BorderPane>
|
||||
<BorderPane><left><Label text="%settings.launcher.language" BorderPane.alignment="CENTER_LEFT" /></left><right><JFXComboBox fx:id="cboLanguage" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="%settings.launcher.proxy" BorderPane.alignment="CENTER_LEFT" /></left><right><HBox alignment="CENTER_LEFT" spacing="5">
|
||||
<JFXComboBox fx:id="cboProxyType">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<Label text="%settings.launcher.proxy.direct" />
|
||||
<Label text="HTTP" />
|
||||
<Label text="Socks" />
|
||||
</FXCollections>
|
||||
</items>
|
||||
</JFXComboBox>
|
||||
<Label text="%settings.launcher.proxy.host" />
|
||||
<JFXTextField fx:id="txtProxyHost" maxWidth="50" />
|
||||
<Label text="%settings.launcher.proxy.port" />
|
||||
<JFXTextField fx:id="txtProxyPort" maxWidth="50" />
|
||||
<Label text="%settings.launcher.proxy.username" />
|
||||
<JFXTextField fx:id="txtProxyUsername" maxWidth="50" />
|
||||
<Label text="%settings.launcher.proxy.password" />
|
||||
<JFXTextField fx:id="txtProxyPassword" maxWidth="50" />
|
||||
</HBox></right></BorderPane>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<Label text="%settings.launcher.language" BorderPane.alignment="CENTER_LEFT" />
|
||||
</left>
|
||||
<right>
|
||||
<JFXComboBox fx:id="cboLanguage" FXUtils.limitWidth="400" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
<ComponentList title="%settings.launcher.proxy"> <!-- proxy -->
|
||||
<VBox spacing="10">
|
||||
<JFXRadioButton fx:id="chkNoProxy" text="%settings.launcher.proxy.no_proxy" />
|
||||
<JFXRadioButton fx:id="chkManualProxy" text="%settings.launcher.proxy.has_proxy" />
|
||||
<VBox fx:id="proxyPane" style="-fx-padding: 0 0 0 30;">
|
||||
<HBox>
|
||||
<JFXRadioButton fx:id="chkProxyHttp" text="%settings.launcher.proxy.http" />
|
||||
<JFXRadioButton fx:id="chkProxySocks" text="%settings.launcher.proxy.socks" />
|
||||
</HBox>
|
||||
<GridPane hgap="20" vgap="10" style="-fx-padding: 0 0 0 15;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints />
|
||||
<ColumnConstraints hgrow="ALWAYS" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints />
|
||||
<RowConstraints />
|
||||
</rowConstraints>
|
||||
|
||||
<Label text="%settings.launcher.proxy.host" GridPane.rowIndex="1" GridPane.columnIndex="0" GridPane.halignment="RIGHT" />
|
||||
<JFXTextField fx:id="txtProxyHost" styleClass="fit-width" GridPane.rowIndex="1" GridPane.columnIndex="1" />
|
||||
|
||||
<Label text="%settings.launcher.proxy.port" GridPane.rowIndex="2" GridPane.columnIndex="0" GridPane.halignment="RIGHT" />
|
||||
<JFXTextField fx:id="txtProxyPort" styleClass="fit-width" GridPane.rowIndex="2" GridPane.columnIndex="1" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<NumberValidator message="%input.number" nullable="false" />
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
|
||||
</GridPane>
|
||||
|
||||
<VBox style="-fx-padding: 20 0 20 5;">
|
||||
<JFXCheckBox fx:id="chkProxyAuthentication" text="%settings.launcher.proxy.authentication" />
|
||||
</VBox>
|
||||
|
||||
<GridPane fx:id="authPane" hgap="20" vgap="10" style="-fx-padding: 0 0 0 15;">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints />
|
||||
<ColumnConstraints hgrow="ALWAYS" />
|
||||
</columnConstraints>
|
||||
|
||||
<rowConstraints>
|
||||
<RowConstraints />
|
||||
<RowConstraints />
|
||||
</rowConstraints>
|
||||
|
||||
<Label fx:id="" text="%settings.launcher.proxy.username" GridPane.rowIndex="0" GridPane.columnIndex="0" />
|
||||
<JFXTextField fx:id="txtProxyUsername" styleClass="fit-width" GridPane.rowIndex="0" GridPane.columnIndex="1" />
|
||||
<Label text="%settings.launcher.proxy.password" GridPane.rowIndex="1" GridPane.columnIndex="0" />
|
||||
<JFXPasswordField fx:id="txtProxyPassword" styleClass="fit-width" GridPane.rowIndex="1" GridPane.columnIndex="1" />
|
||||
</GridPane>
|
||||
</VBox>
|
||||
</VBox>
|
||||
</ComponentList>
|
||||
|
||||
<MultiFileItem fx:id="themeItem" title="%settings.launcher.theme" hasSubtitle="true" hasCustom="false" />
|
||||
|
||||
@@ -88,17 +133,17 @@
|
||||
<RowConstraints minHeight="-Infinity" valignment="TOP" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="-Infinity" valignment="TOP" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<Label text="%about.copyright" style="-fx-font-weight: bold;" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label text="%about.copyright" GridPane.columnIndex="0" GridPane.rowIndex="0" />
|
||||
<Label wrapText="true" text="%about.copyright.statement" GridPane.columnIndex="1" GridPane.rowIndex="0" />
|
||||
<Label text="%about.author" style="-fx-font-weight: bold;" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
<Label text="%about.author" GridPane.columnIndex="0" GridPane.rowIndex="1" />
|
||||
<Label wrapText="true" text="%about.author.statement" GridPane.columnIndex="1" GridPane.rowIndex="1" />
|
||||
<Label text="%about.thanks_to" style="-fx-font-weight: bold;" GridPane.columnIndex="0" GridPane.rowIndex="2" />
|
||||
<Label text="%about.thanks_to" GridPane.columnIndex="0" GridPane.rowIndex="2" />
|
||||
<Label wrapText="true" text="%about.thanks_to.statement" GridPane.columnIndex="1" GridPane.rowIndex="2" />
|
||||
<Label text="%about.dependency" style="-fx-font-weight: bold;" GridPane.columnIndex="0" GridPane.rowIndex="3" />
|
||||
<Label text="%about.dependency" GridPane.columnIndex="0" GridPane.rowIndex="3" />
|
||||
<Label wrapText="true" text="%about.dependency.statement" GridPane.columnIndex="1" GridPane.rowIndex="3" />
|
||||
<Label text="%about.claim" style="-fx-font-weight: bold;" GridPane.columnIndex="0" GridPane.rowIndex="4" />
|
||||
<Label text="%about.claim" GridPane.columnIndex="0" GridPane.rowIndex="4" />
|
||||
<Label textAlignment="JUSTIFY" wrapText="true" text="%about.claim.statement" GridPane.columnIndex="1" GridPane.rowIndex="4" />
|
||||
<Label text="%about.open_source" style="-fx-font-weight: bold;" GridPane.columnIndex="0" GridPane.rowIndex="5" />
|
||||
<Label text="%about.open_source" GridPane.columnIndex="0" GridPane.rowIndex="5" />
|
||||
<Label wrapText="true" text="%about.open_source.statement" GridPane.columnIndex="1" GridPane.rowIndex="5" />
|
||||
</GridPane>
|
||||
</StackPane>
|
||||
|
||||
@@ -10,9 +10,10 @@
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.control.ScrollPane?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane">
|
||||
type="StackPane" FXUtils.limitWidth="400" FXUtils.limitHeight="200">
|
||||
<BorderPane>
|
||||
<top>
|
||||
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" />
|
||||
|
||||
@@ -6,24 +6,17 @@
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.control.Tooltip?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane" pickOnBounds="false">
|
||||
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;">
|
||||
<BorderPane>
|
||||
<top>
|
||||
<HBox alignment="CENTER_RIGHT">
|
||||
</HBox>
|
||||
</top>
|
||||
<center>
|
||||
<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: 10;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
</VBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
<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: 11; -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>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<?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"
|
||||
styleClass="transparent"
|
||||
@@ -13,10 +14,10 @@
|
||||
<BorderPane pickOnBounds="false">
|
||||
<left>
|
||||
<HBox alignment="CENTER" mouseTransparent="true">
|
||||
<StackPane fx:id="imageViewContainer">
|
||||
<ImageView preserveRatio="true" fx:id="imageView" smooth="false">
|
||||
<Image url="/assets/img/icon.png" />
|
||||
</ImageView>
|
||||
<StackPane fx:id="imageViewContainer" FXUtils.limitWidth="32" FXUtils.limitHeight="32">
|
||||
<ImageView preserveRatio="true" fx:id="imageView" smooth="false">
|
||||
<Image url="/assets/img/icon.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
<BorderPane style="-fx-padding: 0 0 0 10;">
|
||||
<top>
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.MultiFileItem?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.NumberValidator?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<StackPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.jackhuang.hmcl.ui.VersionSettingsController">
|
||||
@@ -56,7 +58,11 @@
|
||||
</VBox>
|
||||
</left>
|
||||
<right>
|
||||
<JFXTextField fx:id="txtMaxMemory" BorderPane.alignment="CENTER_RIGHT"/>
|
||||
<JFXTextField fx:id="txtMaxMemory" BorderPane.alignment="CENTER_RIGHT" FXUtils.validateWhileTextChanged="true" FXUtils.limitWidth="300">
|
||||
<validators>
|
||||
<NumberValidator message="%input.number" nullable="false" />
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
@@ -65,7 +71,7 @@
|
||||
<Label text="%settings.advanced.launcher_visible" BorderPane.alignment="CENTER_LEFT"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXComboBox fx:id="cboLauncherVisibility" BorderPane.alignment="CENTER_RIGHT">
|
||||
<JFXComboBox fx:id="cboLauncherVisibility" BorderPane.alignment="CENTER_RIGHT" FXUtils.limitWidth="300">
|
||||
<items>
|
||||
<FXCollections fx:factory="observableArrayList">
|
||||
<Label text="%settings.advanced.launcher_visibility.close"/>
|
||||
@@ -86,9 +92,17 @@
|
||||
<BorderPane>
|
||||
<left>
|
||||
<HBox prefWidth="210" spacing="3" alignment="CENTER" BorderPane.alignment="CENTER">
|
||||
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100"/>
|
||||
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<NumberValidator message="%input.number" nullable="false" />
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<Label>x</Label>
|
||||
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100"/>
|
||||
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<NumberValidator message="%input.number" nullable="false" />
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
@@ -108,7 +122,7 @@
|
||||
<Label text="%settings.show_log"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXToggleButton fx:id="chkShowLogs" size="7"/>
|
||||
<JFXToggleButton fx:id="chkShowLogs" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
</ComponentList>
|
||||
@@ -128,7 +142,11 @@
|
||||
<JFXTextField labelFloat="true" promptText="%settings.advanced.minecraft_arguments"
|
||||
styleClass="fit-width" fx:id="txtGameArgs" StackPane.margin="$insets"/>
|
||||
<JFXTextField labelFloat="true" promptText="%settings.advanced.java_permanent_generation_space"
|
||||
styleClass="fit-width" fx:id="txtMetaspace" StackPane.margin="$insets"/>
|
||||
styleClass="fit-width" fx:id="txtMetaspace" StackPane.margin="$insets" FXUtils.validateWhileTextChanged="true">
|
||||
<validators>
|
||||
<NumberValidator message="%input.number" nullable="true" />
|
||||
</validators>
|
||||
</JFXTextField>
|
||||
<JFXTextField labelFloat="true" promptText="%settings.advanced.wrapper_launcher" styleClass="fit-width"
|
||||
fx:id="txtWrapper" StackPane.margin="$insets"/>
|
||||
<JFXTextField labelFloat="true" promptText="%settings.advanced.precall_command" styleClass="fit-width"
|
||||
@@ -140,7 +158,7 @@
|
||||
<Label text="%settings.advanced.no_jvm_args"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXToggleButton fx:id="chkNoJVMArgs" size="7"/>
|
||||
<JFXToggleButton fx:id="chkNoJVMArgs" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
<BorderPane>
|
||||
@@ -148,7 +166,7 @@
|
||||
<Label text="%settings.advanced.no_common"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXToggleButton fx:id="chkNoCommon" size="7"/>
|
||||
<JFXToggleButton fx:id="chkNoCommon" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
<BorderPane>
|
||||
@@ -156,7 +174,7 @@
|
||||
<Label text="%settings.advanced.dont_check_game_completeness"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXToggleButton fx:id="chkNoGameCheck" size="7"/>
|
||||
<JFXToggleButton fx:id="chkNoGameCheck" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
</ComponentList>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -34,11 +34,17 @@ account.current=Current account
|
||||
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.invalid_password=Invalid password
|
||||
account.failed.invalid_token=Please log out and re-input your password to log in.
|
||||
account.failed.no_charactor=No character in this account.
|
||||
account.failed.wrong_password=Incorrect password or username
|
||||
account.injector.add=Add new authentication server
|
||||
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_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
|
||||
@@ -125,7 +131,9 @@ folder.saves=Saves
|
||||
folder.screenshots=Screenshots
|
||||
|
||||
input.email=The username must be an e-mail.
|
||||
input.number=Must be a number.
|
||||
input.not_empty=Input Requrired!
|
||||
input.url=Must be a valid URL.
|
||||
|
||||
install=Install New Game
|
||||
install.failed=Failed to install
|
||||
@@ -179,7 +187,6 @@ launcher=Launcher
|
||||
launcher.background=Background Image
|
||||
launcher.background.choose=Choose background path.
|
||||
launcher.background.default=Default
|
||||
launcher.background.tooltip=The laucher uses a default background.\nIf you use custom background.png, link it and it will be used.\nIf there is "bg" subdirectory, this app will chooses one picture in "bgskin" randomly.\nIf you set the background setting, this app will use it.
|
||||
launcher.common_directory=Common Directory
|
||||
launcher.common_directory.choose=Choose common directory.
|
||||
launcher.contact=Contact Us
|
||||
@@ -296,13 +303,14 @@ operation.confirm_stop=Terminate the operations?
|
||||
operation.stopped=The operation was aborted.
|
||||
|
||||
profile=Profile
|
||||
profile.copy_from=Copy From:
|
||||
profile.default=Current directory
|
||||
profile.home=User home
|
||||
profile.instance_directory=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=Current directory
|
||||
profile.selected=Selected
|
||||
|
||||
selector.choose=Choose
|
||||
selector.choose_file=Select a file
|
||||
@@ -351,10 +359,14 @@ settings.launcher.download_source=Download Source
|
||||
settings.launcher.language=Language
|
||||
settings.launcher.log_font=Log Font
|
||||
settings.launcher.proxy=Proxy
|
||||
settings.launcher.proxy.direct=Direct
|
||||
settings.launcher.proxy.authentication=Proxy Authentication
|
||||
settings.launcher.proxy.no_proxy=No proxy
|
||||
settings.launcher.proxy.has_proxy=Proxy configuration
|
||||
settings.launcher.proxy.host=Host
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=Password
|
||||
settings.launcher.proxy.port=Port
|
||||
settings.launcher.proxy.socks=Socks
|
||||
settings.launcher.proxy.username=Account
|
||||
settings.launcher.theme=Theme
|
||||
|
||||
@@ -400,11 +412,11 @@ version.manage.rename.message=Please enter the new name
|
||||
version.settings=Settings
|
||||
version.update=Update
|
||||
|
||||
wizard.<_prev=< Prev
|
||||
wizard.prev=< Prev
|
||||
wizard.close=Close
|
||||
wizard.failed=Failed
|
||||
wizard.finish=Finish
|
||||
wizard.help=Help
|
||||
wizard.next_>=Next >
|
||||
wizard.next=Next >
|
||||
wizard.steps=Steps
|
||||
wizard.summary=Summary
|
||||
@@ -34,11 +34,17 @@ account.current=当前账户
|
||||
account.create=新建账户
|
||||
account.email=邮箱
|
||||
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
|
||||
account.failed.invalid_credentials=您的用户名或密码错误,或者登录次数过多被暂时禁止登录,请稍后再试
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.no_charactor=该帐号没有角色
|
||||
account.failed.wrong_password=可能是您的用户名或密码错误
|
||||
account.injector.add=添加登录认证服务器
|
||||
account.injector.http=警告:此服务器使用不安全的http协议,您的密码在登录时会被明文传输。
|
||||
account.injector.server=认证服务器
|
||||
account.injector.server_ip=服务器地址
|
||||
account.injector.server_name=服务器名称
|
||||
account.methods=登录方式
|
||||
account.methods.authlib_injector=authlib-injector 登录
|
||||
account.methods.no_method=没有登入方式...
|
||||
account.methods.offline=离线模式
|
||||
account.methods.yggdrasil=正版登录
|
||||
@@ -125,7 +131,9 @@ folder.saves=存档文件夹
|
||||
folder.screenshots=截图文件夹
|
||||
|
||||
input.email=用户名必须是邮箱
|
||||
input.number=必须是数字
|
||||
input.not_empty=必填项
|
||||
input.url=必须是合法的链接
|
||||
|
||||
install=添加游戏
|
||||
install.failed=安装失败
|
||||
@@ -178,8 +186,7 @@ launch.wrong_javadir=错误的Java路径,将自动重置为默认Java路径。
|
||||
launcher=启动器
|
||||
launcher.background=背景地址
|
||||
launcher.background.choose=选择背景路径
|
||||
launcher.background.default=默认
|
||||
launcher.background.tooltip=启动器默认使用自带的背景\n如果当前目录有background.png,则会使用该文件作为背景\n如果当前目录有bg子目录,则会随机使用里面的一张图作为背景\n如果该背景地址被修改,则会使用背景地址里的一张图作为背景\n背景地址允许有多个地址,使用半角分号";"(不包含双引号)分隔
|
||||
launcher.background.default=默认(自动检索启动器同目录下的background.png/jpg及bg文件夹内的图片)
|
||||
launcher.common_directory=公共文件夹
|
||||
launcher.common_directory.choose=选择公共文件夹
|
||||
launcher.contact=联系我们
|
||||
@@ -296,13 +303,14 @@ operation.confirm_stop=真的要终止操作吗?
|
||||
operation.stopped=操作被强行终止
|
||||
|
||||
profile=配置
|
||||
profile.copy_from=复制配置:
|
||||
profile.default=当前目录
|
||||
profile.home=主文件夹
|
||||
profile.instance_directory=游戏路径
|
||||
profile.new=新建配置
|
||||
profile.new_name=新配置名:
|
||||
profile.remove=真的要删除配置%s吗?
|
||||
profile.title=游戏目录
|
||||
profile.selected=当前目录
|
||||
profile.selected=已选中
|
||||
|
||||
selector.choose=选择
|
||||
selector.choose_file=选择文件
|
||||
@@ -351,10 +359,14 @@ settings.launcher.download_source=下载源
|
||||
settings.launcher.language=语言
|
||||
settings.launcher.log_font=日志字体
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.direct=直连
|
||||
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=主题
|
||||
|
||||
@@ -400,11 +412,11 @@ version.manage.rename.message=请输入要改成的名字
|
||||
version.settings=游戏设置
|
||||
version.update=更新
|
||||
|
||||
wizard.<_prev=< 上一步
|
||||
wizard.prev=< 上一步
|
||||
wizard.close=关闭
|
||||
wizard.failed=失败
|
||||
wizard.finish=完成
|
||||
wizard.help=帮助
|
||||
wizard.next_>=下一步 >
|
||||
wizard.next=下一步 >
|
||||
wizard.steps=步骤
|
||||
wizard.summary=概要
|
||||
@@ -57,6 +57,12 @@ public abstract class Account {
|
||||
*/
|
||||
public abstract AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) throws AuthenticationException;
|
||||
|
||||
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password) throws AuthenticationException {
|
||||
return logInWithPassword(selector, password, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public abstract AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException;
|
||||
|
||||
public abstract boolean canPlayOffline();
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,10 +28,14 @@ import java.util.Map;
|
||||
public abstract class AccountFactory<T extends Account> {
|
||||
|
||||
public final T fromUsername(String username) {
|
||||
return fromUsername(username, "");
|
||||
return fromUsername(username, "", null);
|
||||
}
|
||||
|
||||
public abstract T fromUsername(String username, String password);
|
||||
public final T fromUsername(String username, String password) {
|
||||
return fromUsername(username, password, null);
|
||||
}
|
||||
|
||||
public abstract T fromUsername(String username, String password, Object additionalData);
|
||||
|
||||
protected abstract T fromStorageImpl(Map<Object, Object> storage);
|
||||
|
||||
|
||||
@@ -18,12 +18,15 @@
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@Immutable
|
||||
public final class AuthInfo {
|
||||
|
||||
private final String username;
|
||||
@@ -32,6 +35,7 @@ public final class AuthInfo {
|
||||
private final UserType userType;
|
||||
private final String userProperties;
|
||||
private final String userPropertyMap;
|
||||
private final Arguments arguments;
|
||||
|
||||
public AuthInfo(String username, String userId, String authToken) {
|
||||
this(username, userId, authToken, UserType.LEGACY);
|
||||
@@ -46,12 +50,17 @@ public final class AuthInfo {
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap) {
|
||||
this(username, userId, authToken, userType, userProperties, userPropertyMap, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap, Arguments arguments) {
|
||||
this.username = username;
|
||||
this.userId = userId;
|
||||
this.authToken = authToken;
|
||||
this.userType = userType;
|
||||
this.userProperties = userProperties;
|
||||
this.userPropertyMap = userPropertyMap;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public AuthInfo(GameProfile profile, String authToken, UserType userType, String userProperties) {
|
||||
@@ -93,4 +102,12 @@ public final class AuthInfo {
|
||||
public String getUserPropertyMap() {
|
||||
return userPropertyMap;
|
||||
}
|
||||
|
||||
public Arguments getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public AuthInfo setArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, userId, authToken, userType, userProperties, userPropertyMap, arguments);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,11 @@ public class OfflineAccount extends Account {
|
||||
return new AuthInfo(username, uuid, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
|
||||
return logIn(selector, proxy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
// Offline account need not log out.
|
||||
|
||||
@@ -32,7 +32,7 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount fromUsername(String username, String password) {
|
||||
public OfflineAccount fromUsername(String username, String password, Object additionalData) {
|
||||
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.MultiCharacterSelector;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
private final String serverBaseURL;
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||
|
||||
public AuthlibInjectorAccount(ExceptionalSupplier<String, ?> injectorJarPath, String serverBaseURL, String username) {
|
||||
super(serverBaseURL + "authserver/", serverBaseURL + "sessionserver/", username);
|
||||
|
||||
this.injectorJarPath = injectorJarPath;
|
||||
this.serverBaseURL = serverBaseURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) 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()));
|
||||
|
||||
AuthInfo info = super.logIn(selector, proxy);
|
||||
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())));
|
||||
|
||||
return info.setArguments(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("Unable to get authlib injector jar path", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) 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()));
|
||||
|
||||
AuthInfo info = super.logInWithPassword(selector, password, proxy);
|
||||
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())));
|
||||
|
||||
return info.setArguments(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("Unable to get authlib injector jar path", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> toStorageImpl() {
|
||||
Map<Object, Object> map = super.toStorageImpl();
|
||||
map.put(STORAGE_KEY_SERVER_BASE_URL, serverBaseURL);
|
||||
return map;
|
||||
}
|
||||
|
||||
public String getServerBaseURL() {
|
||||
return serverBaseURL;
|
||||
}
|
||||
|
||||
public static final String STORAGE_KEY_SERVER_BASE_URL = "serverBaseURL";
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount.STORAGE_KEY_SERVER_BASE_URL;
|
||||
import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
|
||||
|
||||
public class AuthlibInjectorAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
|
||||
|
||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier) {
|
||||
this.injectorJarPathSupplier = injectorJarPathSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount fromUsername(String username, String password, Object additionalData) {
|
||||
if (!(additionalData instanceof String) || !NetworkUtils.isURL((String) additionalData))
|
||||
throw new IllegalArgumentException("Additional data should be server base url string for authlib injector accounts.");
|
||||
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(injectorJarPathSupplier, (String) additionalData, username);
|
||||
account.setPassword(password);
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount fromStorageImpl(Map<Object, Object> storage) {
|
||||
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME));
|
||||
String serverBaseURL = Lang.get(storage, STORAGE_KEY_SERVER_BASE_URL, String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_SERVER_BASE_URL));
|
||||
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(injectorJarPathSupplier, serverBaseURL, username);
|
||||
account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class, username));
|
||||
account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null));
|
||||
account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_CLIENT_TOKEN)));
|
||||
|
||||
Lang.get(storage, STORAGE_KEY_USER_PROPERTIES, List.class)
|
||||
.ifPresent(account.getUserProperties()::fromList);
|
||||
Optional<String> profileId = Lang.get(storage, STORAGE_KEY_PROFILE_ID, String.class);
|
||||
Optional<String> profileName = Lang.get(storage, STORAGE_KEY_PROFILE_NAME, String.class);
|
||||
GameProfile profile = null;
|
||||
if (profileId.isPresent() && profileName.isPresent()) {
|
||||
profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get());
|
||||
Lang.get(storage, STORAGE_KEY_PROFILE_PROPERTIES, List.class)
|
||||
.ifPresent(profile.getProperties()::fromList);
|
||||
}
|
||||
account.setSelectedProfile(profile);
|
||||
return account;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Immutable
|
||||
public final class AuthlibInjectorBuildInfo {
|
||||
|
||||
private final int buildNumber;
|
||||
private final String url;
|
||||
|
||||
public AuthlibInjectorBuildInfo() {
|
||||
this(0, "");
|
||||
}
|
||||
|
||||
public AuthlibInjectorBuildInfo(int buildNumber, String url) {
|
||||
this.buildNumber = buildNumber;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public int getBuildNumber() {
|
||||
return buildNumber;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public static AuthlibInjectorBuildInfo requestBuildInfo() throws IOException, JsonParseException {
|
||||
return requestBuildInfo(UPDATE_URL);
|
||||
}
|
||||
|
||||
public static AuthlibInjectorBuildInfo requestBuildInfo(String updateUrl) throws IOException, JsonParseException {
|
||||
return Lang.requireJsonNonNull(Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(updateUrl)), AuthlibInjectorBuildInfo.class));
|
||||
}
|
||||
|
||||
public static final String UPDATE_URL = "https://authlib-injector.to2mbn.org/api/buildInfo";
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
public class AuthlibInjectorServerInfo {
|
||||
private final String serverIp;
|
||||
private final String serverName;
|
||||
|
||||
public AuthlibInjectorServerInfo(String serverIp, String serverName) {
|
||||
this.serverIp = serverIp;
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public String getServerIp() {
|
||||
return serverIp;
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
public class AuthlibInjectorServerResponse {
|
||||
|
||||
private final Meta meta;
|
||||
|
||||
public AuthlibInjectorServerResponse() {
|
||||
this(new Meta());
|
||||
}
|
||||
|
||||
public AuthlibInjectorServerResponse(Meta meta) {
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
public Meta getMeta() {
|
||||
return meta;
|
||||
}
|
||||
|
||||
public static class Meta {
|
||||
private final String serverName;
|
||||
|
||||
public Meta() {
|
||||
this("");
|
||||
}
|
||||
|
||||
public Meta(String serverName) {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public String getServerName() {
|
||||
return serverName;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import java.util.*;
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public final class YggdrasilAccount extends Account {
|
||||
public class YggdrasilAccount extends Account {
|
||||
|
||||
private final String username;
|
||||
private String password;
|
||||
@@ -48,8 +48,15 @@ public final class YggdrasilAccount extends Account {
|
||||
private GameProfile[] profiles;
|
||||
private UserType userType = UserType.LEGACY;
|
||||
|
||||
public YggdrasilAccount(String username) {
|
||||
public YggdrasilAccount(String baseAuthServer, String baseSessionServer, String username) {
|
||||
this.baseAuthServer = baseAuthServer;
|
||||
this.baseSessionServer = baseSessionServer;
|
||||
this.baseProfile = baseSessionServer + "session/minecraft/profile/";
|
||||
this.username = username;
|
||||
|
||||
this.routeAuthenticate = NetworkUtils.toURL(baseAuthServer + "authenticate");
|
||||
this.routeRefresh = NetworkUtils.toURL(baseAuthServer + "refresh");
|
||||
this.routeValidate = NetworkUtils.toURL(baseAuthServer + "validate");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,24 +132,47 @@ public final class YggdrasilAccount extends Account {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthInfo logInWithPassword(MultiCharacterSelector selector, String password, Proxy proxy) throws AuthenticationException {
|
||||
logInWithPassword0(password, proxy);
|
||||
if (!isLoggedIn())
|
||||
throw new AuthenticationException("Wrong password for account " + username);
|
||||
|
||||
if (selectedProfile == null) {
|
||||
if (profiles == null || profiles.length <= 0)
|
||||
throw new NoCharacterException(this);
|
||||
|
||||
selectedProfile = selector.select(this, Arrays.asList(profiles));
|
||||
}
|
||||
return new AuthInfo(selectedProfile, accessToken, userType, GSON.toJson(userProperties));
|
||||
}
|
||||
|
||||
private void logIn0(Proxy proxy) throws AuthenticationException {
|
||||
if (StringUtils.isNotBlank(accessToken)) {
|
||||
if (StringUtils.isBlank(userId))
|
||||
if (StringUtils.isNotBlank(username))
|
||||
userId = username;
|
||||
else
|
||||
throw new AuthenticationException("Invalid uuid and username");
|
||||
if (checkTokenValidity(proxy)) {
|
||||
isOnline = true;
|
||||
return;
|
||||
}
|
||||
logIn1(ROUTE_REFRESH, new RefreshRequest(accessToken, clientToken), proxy);
|
||||
logInWithToken(proxy);
|
||||
} else if (StringUtils.isNotBlank(password))
|
||||
logIn1(ROUTE_AUTHENTICATE, new AuthenticationRequest(username, password, clientToken), proxy);
|
||||
logInWithPassword0(password, proxy);
|
||||
else
|
||||
throw new AuthenticationException("Password cannot be blank");
|
||||
}
|
||||
|
||||
private void logInWithToken(Proxy proxy) throws AuthenticationException {
|
||||
if (StringUtils.isBlank(userId))
|
||||
if (StringUtils.isNotBlank(username))
|
||||
userId = username;
|
||||
else
|
||||
throw new AuthenticationException("Invalid uuid and username");
|
||||
if (checkTokenValidity(proxy)) {
|
||||
isOnline = true;
|
||||
return;
|
||||
}
|
||||
logIn1(routeRefresh, new RefreshRequest(accessToken, clientToken, getSelectedProfile()), proxy);
|
||||
}
|
||||
|
||||
public void logInWithPassword0(String password, Proxy proxy) throws AuthenticationException {
|
||||
logIn1(routeAuthenticate, new AuthenticationRequest(username, password, clientToken), proxy);
|
||||
}
|
||||
|
||||
private void logIn1(URL url, Object input, Proxy proxy) throws AuthenticationException {
|
||||
AuthenticationResponse response = makeRequest(url, input, proxy)
|
||||
.orElseThrow(() -> new AuthenticationException("Server response empty"));
|
||||
@@ -250,7 +280,7 @@ public final class YggdrasilAccount extends Account {
|
||||
return false;
|
||||
|
||||
try {
|
||||
makeRequest(ROUTE_VALIDATE, new ValidateRequest(accessToken, clientToken), proxy);
|
||||
makeRequest(routeValidate, new ValidateRequest(accessToken, clientToken), proxy);
|
||||
return true;
|
||||
} catch (AuthenticationException e) {
|
||||
return false;
|
||||
@@ -268,10 +298,17 @@ public final class YggdrasilAccount extends Account {
|
||||
if (StringUtils.isBlank(userId))
|
||||
throw new IllegalStateException("Not logged in");
|
||||
|
||||
ProfileResponse response = GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(BASE_PROFILE + UUIDTypeAdapter.fromUUID(profile.getId()))), ProfileResponse.class);
|
||||
if (response.getProperties() == null) return Optional.empty();
|
||||
Property textureProperty = response.getProperties().get("textures");
|
||||
if (textureProperty == null) return Optional.empty();
|
||||
Property textureProperty;
|
||||
|
||||
if (getUserProperties().containsKey("textures"))
|
||||
textureProperty = getUserProperties().get("textures");
|
||||
else {
|
||||
ProfileResponse response = GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(baseProfile + UUIDTypeAdapter.fromUUID(profile.getId()))), ProfileResponse.class);
|
||||
if (response.getProperties() == null) return Optional.empty();
|
||||
textureProperty = response.getProperties().get("textures");
|
||||
if (textureProperty == null) return Optional.empty();
|
||||
getUserProperties().putAll(response.getProperties());
|
||||
}
|
||||
|
||||
TextureResponse texture;
|
||||
String json = new String(Base64.getDecoder().decode(textureProperty.getValue()), Charsets.UTF_8);
|
||||
@@ -287,11 +324,12 @@ public final class YggdrasilAccount extends Account {
|
||||
return "YggdrasilAccount[username=" + getUsername() + "]";
|
||||
}
|
||||
|
||||
private static final String BASE_URL = "https://authserver.mojang.com/";
|
||||
private static final String BASE_PROFILE = "https://sessionserver.mojang.com/session/minecraft/profile/";
|
||||
private static final URL ROUTE_AUTHENTICATE = NetworkUtils.toURL(BASE_URL + "authenticate");
|
||||
private static final URL ROUTE_REFRESH = NetworkUtils.toURL(BASE_URL + "refresh");
|
||||
private static final URL ROUTE_VALIDATE = NetworkUtils.toURL(BASE_URL + "validate");
|
||||
private final String baseAuthServer;
|
||||
private final String baseSessionServer;
|
||||
private final String baseProfile;
|
||||
private final URL routeAuthenticate;
|
||||
private final URL routeRefresh;
|
||||
private final URL routeValidate;
|
||||
|
||||
static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken";
|
||||
static final String STORAGE_KEY_PROFILE_NAME = "displayName";
|
||||
|
||||
+16
-6
@@ -31,15 +31,23 @@ import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
public static final YggdrasilAccountFactory INSTANCE = new YggdrasilAccountFactory();
|
||||
public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
|
||||
private YggdrasilAccountFactory() {
|
||||
private final String baseAuthServer;
|
||||
private final String baseSessionServer;
|
||||
|
||||
public YggdrasilAccountFactory() {
|
||||
this(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER);
|
||||
}
|
||||
|
||||
public YggdrasilAccountFactory(String baseAuthServer, String baseSessionServer) {
|
||||
this.baseAuthServer = baseAuthServer;
|
||||
this.baseSessionServer = baseSessionServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount fromUsername(String username, String password) {
|
||||
YggdrasilAccount account = new YggdrasilAccount(username);
|
||||
public YggdrasilAccount fromUsername(String username, String password, Object additionalData) {
|
||||
YggdrasilAccount account = new YggdrasilAccount(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER, username);
|
||||
account.setPassword(password);
|
||||
return account;
|
||||
}
|
||||
@@ -49,7 +57,7 @@ public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccou
|
||||
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME));
|
||||
|
||||
YggdrasilAccount account = new YggdrasilAccount(username);
|
||||
YggdrasilAccount account = new YggdrasilAccount(baseAuthServer, baseSessionServer, username);
|
||||
account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class, username));
|
||||
account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null));
|
||||
account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class)
|
||||
@@ -69,4 +77,6 @@ public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccou
|
||||
return account;
|
||||
}
|
||||
|
||||
private static final String MOJANG_AUTH_SERVER = "https://authserver.mojang.com/";
|
||||
private static final String MOJANG_SESSION_SERVER = "https://sessionserver.mojang.com/";
|
||||
}
|
||||
@@ -66,6 +66,18 @@ public final class Arguments {
|
||||
return new Arguments(Lang.merge(arguments.getGame(), list), arguments.getJvm());
|
||||
}
|
||||
|
||||
public static Arguments addJVMArguments(Arguments arguments, String... jvmArguments) {
|
||||
return addJVMArguments(arguments, Arrays.asList(jvmArguments));
|
||||
}
|
||||
|
||||
public static Arguments addJVMArguments(Arguments arguments, List<String> jvmArguments) {
|
||||
List<Argument> list = jvmArguments.stream().map(StringArgument::new).collect(Collectors.toList());
|
||||
if (arguments == null)
|
||||
return new Arguments(null, list);
|
||||
else
|
||||
return new Arguments(arguments.getGame(), Lang.merge(arguments.getJvm(), list));
|
||||
}
|
||||
|
||||
public static Arguments merge(Arguments a, Arguments b) {
|
||||
if (a == null)
|
||||
return b;
|
||||
|
||||
@@ -138,10 +138,16 @@ public class DefaultLauncher extends Launcher {
|
||||
configuration.put("${assets_root}", gameAssets.getAbsolutePath());
|
||||
|
||||
res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration));
|
||||
if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(authInfo.getArguments().getJvm(), configuration));
|
||||
|
||||
res.add(version.getMainClass());
|
||||
|
||||
Map<String, Boolean> features = getFeatures();
|
||||
res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getGame).orElseGet(this::getDefaultGameArguments), configuration, features));
|
||||
if (authInfo.getArguments() != null && authInfo.getArguments().getGame() != null && !authInfo.getArguments().getGame().isEmpty())
|
||||
res.addAll(Arguments.parseArguments(authInfo.getArguments().getGame(), configuration, features));
|
||||
|
||||
res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(LinkedList::new), configuration));
|
||||
|
||||
// Optional Minecraft arguments
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.util.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -119,9 +120,7 @@ public final class CurseManifest {
|
||||
*/
|
||||
public static Modpack readCurseForgeModpackManifest(File f) throws IOException, JsonParseException {
|
||||
String json = CompressingUtils.readTextZipEntry(f, "manifest.json");
|
||||
CurseManifest manifest = Constants.GSON.fromJson(json, CurseManifest.class);
|
||||
if (manifest == null)
|
||||
throw new JsonParseException("`manifest.json` not found. Not a valid Curse modpack.");
|
||||
CurseManifest manifest = Lang.requireJsonNonNull(Constants.GSON.fromJson(json, CurseManifest.class));
|
||||
return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(),
|
||||
CompressingUtils.readTextZipEntryQuietly(f, "modlist.html").orElse( "No description"), manifest);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod;
|
||||
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.ImmediateBooleanProperty;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -59,11 +60,16 @@ public final class ModInfo implements Comparable<ModInfo> {
|
||||
activeProperty = new ImmediateBooleanProperty(this, "active", !DISABLED_EXTENSION.equals(FileUtils.getExtension(file))) {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
File f = file.getAbsoluteFile(), newF;
|
||||
File f = ModInfo.this.file.getAbsoluteFile(), newF;
|
||||
if (DISABLED_EXTENSION.equals(FileUtils.getExtension(f)))
|
||||
newF = new File(f.getParentFile(), FileUtils.getNameWithoutExtension(f));
|
||||
else
|
||||
newF = new File(f.getParentFile(), f.getName() + ".disabled");
|
||||
|
||||
if (f.renameTo(newF))
|
||||
ModInfo.this.file = newF;
|
||||
else
|
||||
Logging.LOG.severe("Unable to rename file " + f + " to " + newF);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -208,9 +208,9 @@ public abstract class Task {
|
||||
return new TaskExecutor(this);
|
||||
}
|
||||
|
||||
public final TaskExecutor executor(Function<TaskExecutor, TaskListener> taskListener) {
|
||||
public final TaskExecutor executor(TaskListener taskListener) {
|
||||
TaskExecutor executor = new TaskExecutor(this);
|
||||
executor.addTaskListener(taskListener.apply(executor));
|
||||
executor.addTaskListener(taskListener);
|
||||
return executor;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,13 +66,8 @@ public final class TaskExecutor {
|
||||
public TaskExecutor start() {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
workerQueue.add(scheduler.schedule(() -> {
|
||||
if (executeTasks(Collections.singleton(firstTask)))
|
||||
taskListeners.forEach(TaskListener::onSucceed);
|
||||
else
|
||||
taskListeners.forEach(it -> {
|
||||
it.onTerminate();
|
||||
it.onTerminate(variables);
|
||||
});
|
||||
boolean flag = executeTasks(Collections.singleton(firstTask));
|
||||
taskListeners.forEach(it -> it.onStop(flag, this));
|
||||
}));
|
||||
return this;
|
||||
}
|
||||
@@ -81,14 +76,8 @@ public final class TaskExecutor {
|
||||
taskListeners.forEach(TaskListener::onStart);
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
Future<?> future = scheduler.schedule(() -> {
|
||||
if (!executeTasks(Collections.singleton(firstTask))) {
|
||||
taskListeners.forEach(it -> {
|
||||
it.onTerminate();
|
||||
it.onTerminate(variables);
|
||||
});
|
||||
flag.set(false);
|
||||
} else
|
||||
taskListeners.forEach(TaskListener::onSucceed);
|
||||
flag.set(executeTasks(Collections.singleton(firstTask)));
|
||||
taskListeners.forEach(it -> it.onStop(flag.get(), this));
|
||||
});
|
||||
workerQueue.add(future);
|
||||
Lang.invoke(() -> future.get());
|
||||
@@ -192,6 +181,7 @@ public final class TaskExecutor {
|
||||
// do nothing
|
||||
} catch (Exception e) {
|
||||
lastException = e;
|
||||
variables.set("lastException", e);
|
||||
Logging.LOG.log(Level.FINE, "Task failed: " + task.getName(), e);
|
||||
task.onDone().fireEvent(new TaskEvent(this, task, true));
|
||||
taskListeners.forEach(it -> it.onFailed(task, e));
|
||||
@@ -206,6 +196,10 @@ public final class TaskExecutor {
|
||||
return totTask.get();
|
||||
}
|
||||
|
||||
public AutoTypingMap<String> getVariables() {
|
||||
return variables;
|
||||
}
|
||||
|
||||
private class Invoker implements ExceptionalRunnable<Exception> {
|
||||
|
||||
private final Task task;
|
||||
|
||||
@@ -39,16 +39,12 @@ public abstract class TaskListener implements EventListener {
|
||||
public void onFailed(Task task, Throwable throwable) {
|
||||
}
|
||||
|
||||
public void onTerminate() {
|
||||
}
|
||||
|
||||
public void onTerminate(AutoTypingMap<String> variables) {
|
||||
}
|
||||
|
||||
public void onSucceed() {
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
}
|
||||
|
||||
public static class DefaultTaskListener extends TaskListener {
|
||||
private DefaultTaskListener() {
|
||||
}
|
||||
|
||||
public static final DefaultTaskListener INSTANCE = new DefaultTaskListener();
|
||||
}
|
||||
|
||||
@@ -35,15 +35,15 @@ public final class AutoTypingMap<K> {
|
||||
this.impl = impl;
|
||||
}
|
||||
|
||||
public <V> V get(K key) {
|
||||
public synchronized <V> V get(K key) {
|
||||
return (V) impl.get(key);
|
||||
}
|
||||
|
||||
public <V> Optional<V> getOptional(K key) {
|
||||
public synchronized <V> Optional<V> getOptional(K key) {
|
||||
return Optional.ofNullable(get(key));
|
||||
}
|
||||
|
||||
public void set(K key, Object value) {
|
||||
public synchronized void set(K key, Object value) {
|
||||
if (value != null)
|
||||
impl.put(key, value);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public final class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserial
|
||||
cleaned = cleaned.substring(0, 22) + cleaned.substring(23);
|
||||
return ISO_8601_FORMAT.parse(cleaned);
|
||||
} catch (Exception e) {
|
||||
throw new JsonSyntaxException("Invalid date: " + string, e);
|
||||
throw new JsonParseException("Invalid date: " + string, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -22,6 +25,16 @@ public final class Lang {
|
||||
public static final Consumer EMPTY_CONSUMER = a -> {
|
||||
};
|
||||
|
||||
public static <T> T requireJsonNonNull(T obj) throws JsonParseException {
|
||||
return requireJsonNonNull(obj, "Json object cannot be null.");
|
||||
}
|
||||
|
||||
public static <T> T requireJsonNonNull(T obj, String message) throws JsonParseException {
|
||||
if (obj == null)
|
||||
throw new JsonParseException(message);
|
||||
return obj;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(Pair<K, V>... pairs) {
|
||||
HashMap<K, V> map = new HashMap<>();
|
||||
for (Pair<K, V> pair : pairs)
|
||||
@@ -237,6 +250,17 @@ public final class Lang {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void executeDelayed(Runnable runnable, TimeUnit timeUnit, long timeout, boolean isDaemon) {
|
||||
thread(() -> {
|
||||
try {
|
||||
timeUnit.sleep(timeout);
|
||||
runnable.run();
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
|
||||
}, null, isDaemon);
|
||||
}
|
||||
|
||||
public static Thread thread(Runnable runnable) {
|
||||
return thread(runnable, null);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class Logging {
|
||||
try {
|
||||
FileHandler fileHandler = new FileHandler("hmcl.log");
|
||||
fileHandler.setLevel(Level.FINEST);
|
||||
fileHandler.setFormatter(DefaultFormatter.INSTANCE);
|
||||
LOG.addHandler(fileHandler);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Unable to create hmcl.log, " + e.getMessage());
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -92,7 +93,7 @@ public final class NetworkUtils {
|
||||
|
||||
public static HttpURLConnection createConnection(URL url, Proxy proxy) throws IOException {
|
||||
initHttps();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setConnectTimeout(15000);
|
||||
@@ -187,4 +188,13 @@ public final class NetworkUtils {
|
||||
public static URL toURL(String str) {
|
||||
return Lang.invoke(() -> new URL(str));
|
||||
}
|
||||
|
||||
public static boolean isURL(String str) {
|
||||
try {
|
||||
new URL(str);
|
||||
return true;
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,4 +122,10 @@ public enum OperatingSystem {
|
||||
c.putString(string);
|
||||
Clipboard.getSystemClipboard().setContent(c);
|
||||
}
|
||||
|
||||
public static void forceGC() {
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
Vendored
BIN
Binary file not shown.
+1
-2
@@ -1,6 +1,5 @@
|
||||
#Sun Oct 22 14:32:40 CST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip
|
||||
@@ -78,14 +78,14 @@ if [ -n "$JAVA_HOME" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
path of your Java installation."
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
path of your Java installation."
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
|
||||
Vendored
+2
-2
@@ -27,7 +27,7 @@ echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo path of your Java installation.
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -41,7 +41,7 @@ echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo path of your Java installation.
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
Reference in new issue
Block a user