Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
630f164877 | ||
|
|
38c6bce2c0 | ||
|
|
034734ec66 | ||
|
|
d875eeb22a | ||
|
|
61db6b8fb2 | ||
|
|
945d4cf20d | ||
|
|
631337c451 | ||
|
|
4f51e377ae | ||
|
|
e71c06a0d0 | ||
|
|
13aabde5d9 | ||
|
|
e3f371f457 | ||
|
|
6ee99b5736 | ||
|
|
895188b25f | ||
|
|
9fba923a51 | ||
|
|
37a39098f8 | ||
|
|
6fb548e6c3 | ||
|
|
110b7cb06f | ||
|
|
e9070a77c3 | ||
|
|
475e6e5511 | ||
|
|
eed4c7c11a | ||
|
|
d986d7ac03 | ||
|
|
f640922a50 | ||
|
|
118a6cf0d1 | ||
|
|
72871e6d9a | ||
|
|
61acbcec0d | ||
|
|
06af79e2b4 | ||
|
|
6782602141 | ||
|
|
8df17ae38a | ||
|
|
1645a93adf | ||
|
|
6a04ab37d7 | ||
|
|
27c9abd9ed | ||
|
|
a08be2e11d | ||
|
|
17615b1791 | ||
|
|
d75c3b584a | ||
|
|
20f919b93e | ||
|
|
20244fe5cc | ||
|
|
6a89f0a661 | ||
|
|
3a9efdb03e | ||
|
|
2ae00db94b | ||
|
|
a9261b759b | ||
|
|
6b02dc3cd5 | ||
|
|
afa37c0d2f | ||
|
|
2575f10497 | ||
|
|
27c4a68a13 | ||
|
|
f09cb54894 | ||
|
|
5431cfef79 | ||
|
|
35c0f3cddc | ||
|
|
efe22ca613 | ||
|
|
3408a8ffcf | ||
|
|
60a140efb3 | ||
|
|
bbe584bd91 | ||
|
|
3866377a28 | ||
|
|
ebc0b4b2b0 | ||
|
|
d6c65a320d | ||
|
|
2e46d76165 | ||
|
|
23037da406 | ||
|
|
617261c74d | ||
|
|
6c0d6ee0cf |
No files matched your search
+5
-5
@@ -48,16 +48,16 @@ jar {
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
|
||||
manifest {
|
||||
attributes 'Created-By' : 'Copyright(c) 2013-2017 huangyuhui.',
|
||||
attributes 'Created-By' : 'Copyright(c) 2013-2018 huangyuhui.',
|
||||
'Main-Class' : mainClass,
|
||||
'Multi-Release': "true"
|
||||
}
|
||||
|
||||
doLast {
|
||||
new File("build/signed").mkdirs()
|
||||
ant.signjar(signedjar: archivePath, jar: archivePath,
|
||||
keystore: "HMCL.keystore", storepass: "123456",
|
||||
alias: "HMCL")
|
||||
//new File("build/signed").mkdirs()
|
||||
//ant.signjar(signedjar: archivePath, jar: archivePath,
|
||||
// keystore: "HMCL.keystore", storepass: "123456",
|
||||
// alias: "HMCL")
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
archivePath.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
|
||||
@@ -31,7 +31,6 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
@@ -72,8 +71,11 @@ public final class Launcher extends Application {
|
||||
public static void main(String[] args) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
|
||||
if (!FileUtils.makeDirectory(LOG_DIRECTORY))
|
||||
System.out.println("Unable to create log directory " + LOG_DIRECTORY + ", log files cannot be generated.");
|
||||
|
||||
try {
|
||||
Logging.start();
|
||||
Logging.start(LOG_DIRECTORY);
|
||||
|
||||
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
@@ -114,21 +116,6 @@ public final class Launcher extends Application {
|
||||
});
|
||||
}
|
||||
|
||||
public static File getWorkingDirectory(String folder) {
|
||||
String home = System.getProperty("user.home", ".");
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case LINUX:
|
||||
return new File(home, "." + folder + "/");
|
||||
case WINDOWS:
|
||||
String appdata = System.getenv("APPDATA");
|
||||
return new File(Lang.nonNull(appdata, home), "." + folder + "/");
|
||||
case OSX:
|
||||
return new File(home, "Library/Application Support/" + folder);
|
||||
default:
|
||||
return new File(home, folder + "/");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<File> getCurrentJarFiles() {
|
||||
List<File> result = new LinkedList<>();
|
||||
if (Launcher.class.getClassLoader() instanceof URLClassLoader) {
|
||||
@@ -161,8 +148,9 @@ public final class Launcher extends Application {
|
||||
return String.format(i18n(key), formatArgs);
|
||||
}
|
||||
|
||||
public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft");
|
||||
public static final File HMCL_DIRECTORY = getWorkingDirectory("hmcl");
|
||||
public static final File MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
public static final File HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
public static final File LOG_DIRECTORY = new File(Launcher.HMCL_DIRECTORY, "logs");
|
||||
|
||||
public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
|
||||
public static final String NAME = "HMCL";
|
||||
@@ -172,6 +160,7 @@ public final class Launcher extends Application {
|
||||
public static final IUpgrader UPGRADER = new AppDataUpgrader();
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
|
||||
|
||||
public static final String CONTACT = "http://huangyuhui.duapp.com/hmcl.php";
|
||||
public static final String UPDATE_SERVER = "http://47.101.47.110";
|
||||
public static final String CONTACT = UPDATE_SERVER + "/hmcl.php";
|
||||
public static final String PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
}
|
||||
@@ -17,24 +17,41 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
|
||||
public final class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
private static void checkDirectoryPath() {
|
||||
String currentDirectory = new File("").getAbsolutePath();
|
||||
if (currentDirectory.contains("!")) {
|
||||
System.err.println("Exclamation mark(!) is not allowed in the path where HMCL is in. Forcibly exit.");
|
||||
|
||||
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
|
||||
String message = "Exclamation mark(!) is not allowed in the path where HMCL is in.\nThe path is " + currentDirectory;
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
System.exit(1);
|
||||
} else
|
||||
Launcher.main(args);
|
||||
showErrorAndExit("Exclamation mark(!) is not allowed in the path where HMCL is in.\n"
|
||||
+ "The path is " + currentDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkJavaFX() {
|
||||
try {
|
||||
Class.forName("javafx.application.Application");
|
||||
} catch (ClassNotFoundException e) {
|
||||
showErrorAndExit("JavaFX is missing.\n"
|
||||
+ "If you are using Java 11 or above, please downgrade to Java 8 or 10.\n"
|
||||
+ "If you are using OpenJDK, please ensure OpenJFX is included.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void showErrorAndExit(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("A fatal error has occurred, forcibly exiting.");
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* This event gets fired when added accounts.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class AccountAddedEvent extends Event {
|
||||
|
||||
private final Account account;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public AccountAddedEvent(Object source, Account account) {
|
||||
super(source);
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("account", account)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -34,13 +34,10 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
public final class AccountHelper {
|
||||
public static final AccountHelper INSTANCE = new AccountHelper();
|
||||
|
||||
private AccountHelper() {}
|
||||
|
||||
public static final File SKIN_DIR = new File(Launcher.HMCL_DIRECTORY, "skins");
|
||||
@@ -73,8 +70,8 @@ public final class AccountHelper {
|
||||
return new SkinLoadTask(account, proxy, true);
|
||||
}
|
||||
|
||||
private static File getSkinFile(String name) {
|
||||
return new File(SKIN_DIR, name + ".png");
|
||||
private static File getSkinFile(UUID uuid) {
|
||||
return new File(SKIN_DIR, uuid + ".png");
|
||||
}
|
||||
|
||||
public static Image getSkin(YggdrasilAccount account) {
|
||||
@@ -82,9 +79,11 @@ public final class AccountHelper {
|
||||
}
|
||||
|
||||
public static Image getSkin(YggdrasilAccount account, double scaleRatio) {
|
||||
if (account.getCharacter() == null)
|
||||
return getDefaultSkin(account, scaleRatio);
|
||||
File file = getSkinFile(account.getCharacter());
|
||||
UUID uuid = account.getUUID();
|
||||
if (uuid == null)
|
||||
return getSteveSkin(scaleRatio);
|
||||
|
||||
File file = getSkinFile(uuid);
|
||||
if (file.exists()) {
|
||||
Image original = new Image("file:" + file.getAbsolutePath());
|
||||
return new Image("file:" + file.getAbsolutePath(),
|
||||
@@ -92,15 +91,14 @@ public final class AccountHelper {
|
||||
original.getHeight() * scaleRatio,
|
||||
false, false);
|
||||
}
|
||||
return getDefaultSkin(account, scaleRatio);
|
||||
return getDefaultSkin(uuid, scaleRatio);
|
||||
}
|
||||
|
||||
public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio, Proxy proxy) throws Exception {
|
||||
String name = profile.getName();
|
||||
File file = getSkinFile(name);
|
||||
File file = getSkinFile(profile.getId());
|
||||
downloadSkin(account, profile, true, proxy);
|
||||
if (!file.exists())
|
||||
return getDefaultSkin(account, scaleRatio);
|
||||
return getDefaultSkin(profile.getId(), scaleRatio);
|
||||
|
||||
String url = "file:" + file.getAbsolutePath();
|
||||
return scale(url, scaleRatio);
|
||||
@@ -152,7 +150,7 @@ public final class AccountHelper {
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(profile.getName());
|
||||
File file = getSkinFile(profile.getId());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
@@ -165,7 +163,7 @@ public final class AccountHelper {
|
||||
Optional<Texture> texture = account.getSkin();
|
||||
if (!texture.isPresent()) return;
|
||||
String url = texture.get().getUrl();
|
||||
File file = getSkinFile(account.getCharacter());
|
||||
File file = getSkinFile(account.getUUID());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
@@ -187,11 +185,8 @@ public final class AccountHelper {
|
||||
return scale("/assets/img/alex.png", 4);
|
||||
}
|
||||
|
||||
public static Image getDefaultSkin(Account account, double scaleRatio) {
|
||||
if (account == null)
|
||||
return getSteveSkin(scaleRatio);
|
||||
|
||||
int type = account.getUUID().hashCode() & 1;
|
||||
public static Image getDefaultSkin(UUID uuid, double scaleRatio) {
|
||||
int type = uuid.hashCode() & 1;
|
||||
if (type == 1)
|
||||
return getAlexSkin(scaleRatio);
|
||||
else
|
||||
|
||||
@@ -27,7 +27,6 @@ import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
@@ -38,7 +36,6 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
@@ -47,7 +44,9 @@ import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class LauncherHelper {
|
||||
public static final LauncherHelper INSTANCE = new LauncherHelper();
|
||||
@@ -98,10 +97,8 @@ public final class LauncherHelper {
|
||||
try {
|
||||
variables.set("account", account.logIn());
|
||||
} catch (ServerDisconnectException e) {
|
||||
if (account.canPlayOffline())
|
||||
variables.set("account", account.playOffline());
|
||||
else
|
||||
throw e;
|
||||
variables.set("account",
|
||||
account.playOffline().orElseThrow(() -> e));
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
variables.set("account", DialogController.logIn(account));
|
||||
@@ -110,7 +107,13 @@ 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, gameVersion.isPresent())
|
||||
repository,
|
||||
selectedVersion,
|
||||
variables.get("account"),
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
setting.getLauncherVisibility() == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent())
|
||||
));
|
||||
}))
|
||||
.then(variables -> {
|
||||
@@ -181,6 +184,7 @@ public final class LauncherHelper {
|
||||
if (java == null) {
|
||||
Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
flag = true;
|
||||
}
|
||||
@@ -300,10 +304,10 @@ public final class LauncherHelper {
|
||||
if (authInfo == null)
|
||||
forbiddenTokens = Collections.emptyMap();
|
||||
else
|
||||
forbiddenTokens = Lang.mapOf(
|
||||
new Pair<>(authInfo.getAccessToken(), "<access token>"),
|
||||
new Pair<>(authInfo.getUserId(), "<uuid>"),
|
||||
new Pair<>(authInfo.getUsername(), "<player>")
|
||||
forbiddenTokens = mapOf(
|
||||
pair(authInfo.getAccessToken(), "<access token>"),
|
||||
pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), "<uuid>"),
|
||||
pair(authInfo.getUsername(), "<player>")
|
||||
);
|
||||
|
||||
visibility = setting.getLauncherVisibility();
|
||||
@@ -333,7 +337,7 @@ public final class LauncherHelper {
|
||||
else
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(new Pair<>(log, level));
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > Settings.INSTANCE.getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
|
||||
@@ -32,7 +32,9 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -40,6 +42,9 @@ import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -50,10 +55,10 @@ public final class Accounts {
|
||||
public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil";
|
||||
public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector";
|
||||
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf(
|
||||
new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
||||
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
|
||||
);
|
||||
|
||||
private static final Map<String, String> AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>();
|
||||
|
||||
@@ -17,256 +17,92 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import java.util.*;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
|
||||
public final class Config implements Cloneable {
|
||||
|
||||
@SerializedName("last")
|
||||
private String selectedProfile = "";
|
||||
public final StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("backgroundType")
|
||||
private int backgroundImageType = 0;
|
||||
public final IntegerProperty backgroundImageType = new SimpleIntegerProperty(0);
|
||||
|
||||
@SerializedName("bgpath")
|
||||
private String backgroundImage = null;
|
||||
public final StringProperty backgroundImage = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("commonpath")
|
||||
private String commonDirectory = Launcher.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||
public final StringProperty commonDirectory = new SimpleStringProperty(Launcher.MINECRAFT_DIRECTORY.getAbsolutePath());
|
||||
|
||||
@SerializedName("hasProxy")
|
||||
private boolean hasProxy = false;
|
||||
public final BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("hasProxyAuth")
|
||||
private boolean hasProxyAuth = false;
|
||||
public final BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("proxyType")
|
||||
private int proxyType = 0;
|
||||
public final IntegerProperty proxyType = new SimpleIntegerProperty();
|
||||
|
||||
@SerializedName("proxyHost")
|
||||
private String proxyHost = null;
|
||||
public final StringProperty proxyHost = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPort")
|
||||
private String proxyPort = null;
|
||||
public final StringProperty proxyPort = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyUserName")
|
||||
private String proxyUser = null;
|
||||
public final StringProperty proxyUser = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPassword")
|
||||
private String proxyPass = null;
|
||||
public final StringProperty proxyPass = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("theme")
|
||||
private String theme = null;
|
||||
|
||||
@SerializedName("java")
|
||||
private List<JavaVersion> java = null;
|
||||
public final StringProperty theme = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("localization")
|
||||
private String localization;
|
||||
public final StringProperty localization = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("downloadtype")
|
||||
private int downloadType = 0;
|
||||
public final IntegerProperty downloadType = new SimpleIntegerProperty();
|
||||
|
||||
@SerializedName("configurations")
|
||||
private Map<String, Profile> configurations = new TreeMap<>();
|
||||
public final ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
|
||||
@SerializedName("accounts")
|
||||
private List<Map<Object, Object>> accounts = new LinkedList<>();
|
||||
public final ObservableList<Map<Object, Object>> accounts = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("selectedAccount")
|
||||
private String selectedAccount = "";
|
||||
public final StringProperty selectedAccount = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("fontFamily")
|
||||
private String fontFamily = "Consolas";
|
||||
public final StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
|
||||
@SerializedName("fontSize")
|
||||
private double fontSize = 12;
|
||||
public final DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
|
||||
@SerializedName("logLines")
|
||||
private int logLines = 100;
|
||||
public final IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("authlibInjectorServerURLs")
|
||||
private Set<String> authlibInjectorServerURLs = new HashSet<>();
|
||||
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public void setSelectedProfile(String selectedProfile) {
|
||||
this.selectedProfile = selectedProfile;
|
||||
}
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage = backgroundImage;
|
||||
}
|
||||
|
||||
public int getBackgroundImageType() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(int backgroundImageType) {
|
||||
this.backgroundImageType = backgroundImageType;
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
return commonDirectory;
|
||||
}
|
||||
|
||||
public void setCommonDirectory(String commonDirectory) {
|
||||
this.commonDirectory = commonDirectory;
|
||||
}
|
||||
|
||||
public boolean isHasProxy() {
|
||||
return hasProxy;
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
this.hasProxy = hasProxy;
|
||||
}
|
||||
|
||||
public boolean isHasProxyAuth() {
|
||||
return hasProxyAuth;
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
this.hasProxyAuth = hasProxyAuth;
|
||||
}
|
||||
|
||||
public int getProxyType() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public void setProxyType(int proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
this.proxyHost = proxyHost;
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
this.proxyPort = proxyPort;
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return proxyUser;
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
this.proxyUser = proxyUser;
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return proxyPass;
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
this.proxyPass = proxyPass;
|
||||
}
|
||||
|
||||
public String getTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public void setTheme(String theme) {
|
||||
this.theme = theme;
|
||||
}
|
||||
|
||||
public List<JavaVersion> getJava() {
|
||||
return java;
|
||||
}
|
||||
|
||||
public void setJava(List<JavaVersion> java) {
|
||||
this.java = java;
|
||||
}
|
||||
|
||||
public String getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public void setLocalization(String localization) {
|
||||
this.localization = localization;
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadType) {
|
||||
this.downloadType = downloadType;
|
||||
}
|
||||
|
||||
public Map<String, Profile> getConfigurations() {
|
||||
return configurations;
|
||||
}
|
||||
|
||||
public void setConfigurations(Map<String, Profile> configurations) {
|
||||
this.configurations = configurations;
|
||||
}
|
||||
|
||||
public List<Map<Object, Object>> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void setAccounts(List<Map<Object, Object>> accounts) {
|
||||
this.accounts = accounts;
|
||||
}
|
||||
|
||||
public String getSelectedAccount() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public void setSelectedAccount(String selectedAccount) {
|
||||
this.selectedAccount = selectedAccount;
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily = fontFamily;
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public void setFontSize(double fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return logLines;
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
this.logLines = logLines;
|
||||
}
|
||||
|
||||
public Set<String> getAuthlibInjectorServerURLs() {
|
||||
return authlibInjectorServerURLs;
|
||||
}
|
||||
|
||||
public void setAuthlibInjectorServerURLs(Set<String> authlibInjectorServerURLs) {
|
||||
this.authlibInjectorServerURLs = authlibInjectorServerURLs;
|
||||
}
|
||||
public final ObservableSet<String> authlibInjectorServerURLs = FXCollections.observableSet(new HashSet<>());
|
||||
|
||||
@Override
|
||||
public Config clone() {
|
||||
return Settings.GSON.fromJson(Settings.GSON.toJson(this), Config.class);
|
||||
}
|
||||
|
||||
@@ -22,16 +22,21 @@ import com.google.gson.GsonBuilder;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
import org.hildan.fxgson.creators.ObservableListCreator;
|
||||
import org.hildan.fxgson.creators.ObservableMapCreator;
|
||||
import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.event.AccountLoadingEvent;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
@@ -42,15 +47,23 @@ import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public class Settings {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
|
||||
.setPrettyPrinting().create();
|
||||
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
|
||||
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
|
||||
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
|
||||
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
@@ -60,18 +73,16 @@ public class Settings {
|
||||
|
||||
public static final Settings INSTANCE = new Settings();
|
||||
|
||||
private Settings() {}
|
||||
|
||||
private final Config SETTINGS = initSettings();
|
||||
|
||||
private final Map<String, Account> accounts = new HashMap<>();
|
||||
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
|
||||
|
||||
{
|
||||
private Settings() {
|
||||
loadProxy();
|
||||
|
||||
for (Iterator<Map<Object, Object>> iterator = SETTINGS.getAccounts().iterator(); iterator.hasNext(); ) {
|
||||
for (Iterator<Map<Object, Object>> iterator = SETTINGS.accounts.iterator(); iterator.hasNext();) {
|
||||
Map<Object, Object> settings = iterator.next();
|
||||
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(Lang.get(settings, "type", String.class).orElse(""));
|
||||
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
|
||||
if (factory == null) {
|
||||
// unrecognized account type, so remove it.
|
||||
iterator.remove();
|
||||
@@ -90,7 +101,6 @@ public class Settings {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
checkAuthlibInjectorServerURLs();
|
||||
checkAuthlibInjectorAccounts();
|
||||
checkProfileMap();
|
||||
|
||||
@@ -126,11 +136,11 @@ public class Settings {
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
SETTINGS.getAccounts().clear();
|
||||
SETTINGS.accounts.clear();
|
||||
for (Account account : accounts.values()) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", Accounts.getAccountType(account));
|
||||
SETTINGS.getAccounts().add(storage);
|
||||
SETTINGS.accounts.add(storage);
|
||||
}
|
||||
|
||||
FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS));
|
||||
@@ -139,12 +149,12 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.getCommonDirectory()) {
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.commonDirectory.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setCommonDirectory(get());
|
||||
SETTINGS.commonDirectory.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -161,7 +171,7 @@ public class Settings {
|
||||
this.commonPath.set(commonPath);
|
||||
}
|
||||
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.getLocalization());
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.localization.get());
|
||||
|
||||
public Locales.SupportedLocale getLocale() {
|
||||
return locale;
|
||||
@@ -169,7 +179,7 @@ public class Settings {
|
||||
|
||||
public void setLocale(Locales.SupportedLocale locale) {
|
||||
this.locale = locale;
|
||||
SETTINGS.setLocalization(Locales.getNameByLocale(locale));
|
||||
SETTINGS.localization.set(Locales.getNameByLocale(locale));
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -179,7 +189,7 @@ public class Settings {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.getProxyType());
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.proxyType.get());
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType;
|
||||
@@ -187,58 +197,62 @@ public class Settings {
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
SETTINGS.setProxyType(Proxies.PROXIES.indexOf(proxyType));
|
||||
SETTINGS.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
|
||||
save();
|
||||
loadProxy();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return SETTINGS.getProxyHost();
|
||||
return SETTINGS.proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
SETTINGS.setProxyHost(proxyHost);
|
||||
SETTINGS.proxyHost.set(proxyHost);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return SETTINGS.getProxyPort();
|
||||
return SETTINGS.proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
SETTINGS.setProxyPort(proxyPort);
|
||||
SETTINGS.proxyPort.set(proxyPort);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return SETTINGS.getProxyUser();
|
||||
return SETTINGS.proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
SETTINGS.setProxyUser(proxyUser);
|
||||
SETTINGS.proxyUser.set(proxyUser);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return SETTINGS.getProxyPass();
|
||||
return SETTINGS.proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
SETTINGS.setProxyPass(proxyPass);
|
||||
SETTINGS.proxyPass.set(proxyPass);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxy() { return SETTINGS.isHasProxy(); }
|
||||
public boolean hasProxy() {
|
||||
return SETTINGS.hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
SETTINGS.setHasProxy(hasProxy);
|
||||
SETTINGS.hasProxy.set(hasProxy);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() { return SETTINGS.isHasProxyAuth(); }
|
||||
public boolean hasProxyAuth() {
|
||||
return SETTINGS.hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
SETTINGS.setHasProxyAuth(hasProxyAuth);
|
||||
SETTINGS.hasProxyAuth.set(hasProxyAuth);
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -269,21 +283,21 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return Font.font(SETTINGS.getFontFamily(), SETTINGS.getFontSize());
|
||||
return Font.font(SETTINGS.fontFamily.get(), SETTINGS.fontSize.get());
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
SETTINGS.setFontFamily(font.getFamily());
|
||||
SETTINGS.setFontSize(font.getSize());
|
||||
SETTINGS.fontFamily.set(font.getFamily());
|
||||
SETTINGS.fontSize.set(font.getSize());
|
||||
save();
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return Math.max(SETTINGS.getLogLines(), 100);
|
||||
return Math.max(SETTINGS.logLines.get(), 100);
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
SETTINGS.setLogLines(logLines);
|
||||
SETTINGS.logLines.set(logLines);
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -292,37 +306,26 @@ public class Settings {
|
||||
****************************************/
|
||||
|
||||
public Set<String> getAuthlibInjectorServerURLs() {
|
||||
return SETTINGS.getAuthlibInjectorServerURLs();
|
||||
return SETTINGS.authlibInjectorServerURLs;
|
||||
}
|
||||
|
||||
public void removeAuthlibInjectorServerURL(String serverURL) {
|
||||
checkAuthlibInjectorServerURLs();
|
||||
|
||||
SETTINGS.getAuthlibInjectorServerURLs().remove(serverURL);
|
||||
SETTINGS.authlibInjectorServerURLs.remove(serverURL);
|
||||
|
||||
checkAuthlibInjectorAccounts();
|
||||
save();
|
||||
}
|
||||
|
||||
public void addAuthlibInjectorServerURL(String serverURL) {
|
||||
checkAuthlibInjectorServerURLs();
|
||||
|
||||
SETTINGS.getAuthlibInjectorServerURLs().add(serverURL);
|
||||
SETTINGS.authlibInjectorServerURLs.add(serverURL);
|
||||
save();
|
||||
}
|
||||
|
||||
private void checkAuthlibInjectorServerURLs() {
|
||||
if (SETTINGS.getAuthlibInjectorServerURLs() == null) {
|
||||
SETTINGS.setAuthlibInjectorServerURLs(new HashSet<>());
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAuthlibInjectorAccounts() {
|
||||
for (Account account : getAccounts()) {
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorAccount injectorAccount = (AuthlibInjectorAccount) account;
|
||||
if (!SETTINGS.getAuthlibInjectorServerURLs().contains(injectorAccount.getServerBaseURL()))
|
||||
if (!SETTINGS.authlibInjectorServerURLs.contains(injectorAccount.getServerBaseURL()))
|
||||
deleteAccount(account);
|
||||
}
|
||||
}
|
||||
@@ -333,14 +336,14 @@ public class Settings {
|
||||
****************************************/
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
return DownloadProviders.getDownloadProvider(SETTINGS.getDownloadType());
|
||||
return DownloadProviders.getDownloadProvider(SETTINGS.downloadType.get());
|
||||
}
|
||||
|
||||
public void setDownloadProvider(DownloadProvider downloadProvider) {
|
||||
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
|
||||
if (index == -1)
|
||||
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
|
||||
SETTINGS.setDownloadType(index);
|
||||
SETTINGS.downloadType.set(index);
|
||||
save();
|
||||
}
|
||||
|
||||
@@ -348,7 +351,7 @@ public class Settings {
|
||||
* ACCOUNTS *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.getSelectedAccount())) {
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.selectedAccount.get())) {
|
||||
@Override
|
||||
public Account get() {
|
||||
Account a = super.get();
|
||||
@@ -370,7 +373,7 @@ public class Settings {
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setSelectedAccount(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
SETTINGS.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -390,6 +393,8 @@ public class Settings {
|
||||
public void addAccount(Account account) {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
onAccountLoading();
|
||||
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountAddedEvent(this, account));
|
||||
}
|
||||
|
||||
public Account getAccount(String name, String character) {
|
||||
@@ -418,12 +423,12 @@ public class Settings {
|
||||
* BACKGROUND *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.getBackgroundImage()) {
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.backgroundImage.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setBackgroundImage(get());
|
||||
SETTINGS.backgroundImage.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -440,12 +445,12 @@ public class Settings {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.getBackgroundImageType())) {
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.backgroundImageType.get())) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setBackgroundImageType(get().ordinal());
|
||||
SETTINGS.backgroundImageType.set(get().ordinal());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -466,12 +471,12 @@ public class Settings {
|
||||
* THEME *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.getTheme()).orElse(Theme.BLUE)) {
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.theme.get()).orElse(Theme.BLUE)) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setTheme(get().getName().toLowerCase());
|
||||
SETTINGS.theme.set(get().getName().toLowerCase());
|
||||
save();
|
||||
}
|
||||
};
|
||||
@@ -495,19 +500,19 @@ public class Settings {
|
||||
public Profile getSelectedProfile() {
|
||||
checkProfileMap();
|
||||
|
||||
if (!hasProfile(SETTINGS.getSelectedProfile())) {
|
||||
if (!hasProfile(SETTINGS.selectedProfile.get())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
|
||||
SETTINGS.setSelectedProfile(selectedProfile);
|
||||
SETTINGS.selectedProfile.set(selectedProfile);
|
||||
save();
|
||||
});
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
return getProfile(SETTINGS.getSelectedProfile());
|
||||
return getProfile(SETTINGS.selectedProfile.get());
|
||||
}
|
||||
|
||||
public void setSelectedProfile(Profile selectedProfile) {
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.getSelectedProfile())) {
|
||||
SETTINGS.setSelectedProfile(selectedProfile.getName());
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.selectedProfile.get())) {
|
||||
SETTINGS.selectedProfile.set(selectedProfile.getName());
|
||||
save();
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
@@ -525,7 +530,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
return SETTINGS.getConfigurations();
|
||||
return SETTINGS.configurations;
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
|
||||
@@ -26,7 +26,9 @@ import org.jackhuang.hmcl.util.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -87,6 +89,20 @@ public final class VersionSetting {
|
||||
javaProperty.set(java);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty defaultJavaPathProperty = new ImmediateStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
* Path to Java executable, or null if user customizes java directory.
|
||||
* It's used to determine which JRE to use when multiple JREs match the selected Java version.
|
||||
*/
|
||||
public String getDefaultJavaPath() {
|
||||
return defaultJavaPathProperty.get();
|
||||
}
|
||||
|
||||
public void setDefaultJavaPath(String defaultJavaPath) {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty javaDirProperty = new ImmediateStringProperty(this, "javaDir", "");
|
||||
|
||||
public ImmediateStringProperty javaDirProperty() {
|
||||
@@ -444,12 +460,18 @@ public final class VersionSetting {
|
||||
return null; // Custom Java Directory not found,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
JavaVersion c = JavaVersion.getJREs().get(getJava());
|
||||
if (c == null) {
|
||||
List<JavaVersion> matchedJava = JavaVersion.getJREs().stream()
|
||||
.filter(java -> java.getVersion().equals(getJava()))
|
||||
.collect(Collectors.toList());
|
||||
if (matchedJava.isEmpty()) {
|
||||
setJava("Default");
|
||||
return JavaVersion.fromCurrentEnvironment();
|
||||
} else
|
||||
return c;
|
||||
} else {
|
||||
return matchedJava.stream()
|
||||
.filter(java -> java.getBinary().toString().equals(getDefaultJavaPath()))
|
||||
.findFirst()
|
||||
.orElse(matchedJava.get(0));
|
||||
}
|
||||
} else throw new Error();
|
||||
}
|
||||
|
||||
@@ -475,6 +497,7 @@ public final class VersionSetting {
|
||||
gameDirTypeProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
}
|
||||
|
||||
public LaunchOptions toLaunchOptions(File gameDir) throws InterruptedException {
|
||||
@@ -535,6 +558,7 @@ public final class VersionSetting {
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
|
||||
obj.addProperty("defaultJavaPath", src.getDefaultJavaPath());
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -571,6 +595,7 @@ public final class VersionSetting {
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(1)]);
|
||||
vs.setGameDirType(EnumGameDirectory.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -29,8 +28,6 @@ import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.AddAccountPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
|
||||
@@ -41,6 +41,8 @@ import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
@@ -63,18 +65,23 @@ public class AddAccountPane extends StackPane {
|
||||
@FXML private Label lblPassword;
|
||||
@FXML private JFXComboBox<String> cboType;
|
||||
@FXML private JFXComboBox<TwoLineListItem> cboServers;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
@FXML private Label lblAddInjectorServer;
|
||||
@FXML private Hyperlink linkAddInjectorServer;
|
||||
@FXML private Label lblInjectorServer;
|
||||
@FXML private Hyperlink linkManageInjectorServers;
|
||||
@FXML private JFXDialogLayout layout;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private StackPane acceptPane;
|
||||
@FXML private JFXSpinner spinnerAccept;
|
||||
private final Consumer<Region> finalization;
|
||||
private final TransitionHandler transitionHandler;
|
||||
|
||||
public AddAccountPane(Consumer<Region> finalization) {
|
||||
this.finalization = finalization;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
|
||||
|
||||
transitionHandler = new TransitionHandler(acceptPane);
|
||||
acceptPane.getChildren().setAll(btnAccept);
|
||||
|
||||
loadServers();
|
||||
|
||||
cboType.getItems().setAll(Launcher.i18n("account.methods.offline"), Launcher.i18n("account.methods.yggdrasil"), Launcher.i18n("account.methods.authlib_injector"));
|
||||
@@ -82,8 +89,8 @@ public class AddAccountPane extends StackPane {
|
||||
txtPassword.setVisible(newValue.intValue() != 0);
|
||||
lblPassword.setVisible(newValue.intValue() != 0);
|
||||
cboServers.setVisible(newValue.intValue() == 2);
|
||||
linkAddInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
lblAddInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
linkManageInjectorServers.setVisible(newValue.intValue() == 2);
|
||||
lblInjectorServer.setVisible(newValue.intValue() == 2);
|
||||
validateAcceptButton();
|
||||
});
|
||||
cboType.getSelectionModel().select(0);
|
||||
@@ -122,13 +129,21 @@ public class AddAccountPane extends StackPane {
|
||||
}));
|
||||
}
|
||||
|
||||
private void showSpinner() {
|
||||
transitionHandler.setContent(spinnerAccept, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
private void hideSpinner() {
|
||||
transitionHandler.setContent(btnAccept, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationAccept() {
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
String apiRoot = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()).map(TwoLineListItem::getSubtitle).orElse(null);
|
||||
progressBar.setVisible(true);
|
||||
showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
Task.ofResult("create_account", () -> {
|
||||
AccountFactory<?> factory;
|
||||
@@ -142,7 +157,7 @@ public class AddAccountPane extends StackPane {
|
||||
return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy());
|
||||
}).finalized(Schedulers.javafx(), variables -> {
|
||||
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
||||
progressBar.setVisible(false);
|
||||
hideSpinner();
|
||||
finalization.accept(this);
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
@@ -150,7 +165,7 @@ public class AddAccountPane extends StackPane {
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
progressBar.setVisible(false);
|
||||
hideSpinner();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@@ -160,7 +175,7 @@ public class AddAccountPane extends StackPane {
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddInjecterServer() {
|
||||
private void onManageInjecterServers() {
|
||||
finalization.accept(this);
|
||||
Controllers.navigate(Controllers.getServersPage());
|
||||
}
|
||||
@@ -208,6 +223,7 @@ public class AddAccountPane extends StackPane {
|
||||
try {
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy());
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
|
||||
image = FXUtils.DEFAULT_ICON;
|
||||
}
|
||||
ImageView portraitView = new ImageView();
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -33,7 +31,6 @@ import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -118,7 +115,8 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void dialog(Region content) {
|
||||
decorator.showDialog(content);
|
||||
if (decorator != null)
|
||||
decorator.showDialog(content);
|
||||
}
|
||||
|
||||
public static void dialog(String text) {
|
||||
@@ -171,6 +169,8 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void showUpdate() {
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
getLeftPaneController().showUpdate();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.adapters.ReflectionHelper;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
@@ -47,16 +46,16 @@ import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.ReflectionHelper.call;
|
||||
import static org.jackhuang.hmcl.util.ReflectionHelper.construct;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public final class FXUtils {
|
||||
private FXUtils() {
|
||||
@@ -125,7 +124,7 @@ public final class FXUtils {
|
||||
}
|
||||
|
||||
public static void removeListener(Node node, String key) {
|
||||
Lang.cast(node.getProperties().get(key), ListenerPair.class)
|
||||
tryCast(node.getProperties().get(key), ListenerPair.class)
|
||||
.ifPresent(info -> {
|
||||
info.unbind();
|
||||
node.getProperties().remove(key);
|
||||
@@ -201,12 +200,6 @@ public final class FXUtils {
|
||||
Lang.invoke((ExceptionalSupplier<Object, IOException>) loader::load);
|
||||
}
|
||||
|
||||
public static void resetChildren(JFXMasonryPane pane, List<Node> children) {
|
||||
// Fixes mis-repositioning.
|
||||
ReflectionHelper.setFieldContent(JFXMasonryPane.class, pane, "oldBoxes", null);
|
||||
pane.getChildren().setAll(children);
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, String tooltip) {
|
||||
installTooltip(node, 0, 5000, 0, new Tooltip(tooltip));
|
||||
}
|
||||
@@ -214,17 +207,22 @@ public final class FXUtils {
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
try {
|
||||
// Java 8
|
||||
call(construct(Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"), new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false),
|
||||
"install", node, tooltip);
|
||||
} catch (Throwable e) {
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
try {
|
||||
// Java 9
|
||||
call(tooltip, "setShowDelay", new Duration(openDelay));
|
||||
call(tooltip, "setShowDuration", new Duration(visibleDelay));
|
||||
call(tooltip, "setHideDelay", new Duration(closeDelay));
|
||||
} catch (Throwable e2) {
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip by reflection", e);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
@@ -333,7 +331,7 @@ public final class FXUtils {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
||||
ChangeListener listener = Lang.get(comboBox.getProperties(), "FXUtils.bindEnum.listener", ChangeListener.class).orElse(null);
|
||||
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
|
||||
if (listener == null) return;
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
@@ -50,6 +51,7 @@ import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -60,6 +62,7 @@ public final class LeftPaneController {
|
||||
private final VBox accountPane = new VBox();
|
||||
private final IconedItem launcherSettingsItem;
|
||||
private final VersionListItem missingAccountItem = new VersionListItem(Launcher.i18n("account.missing"), Launcher.i18n("message.unknown"));
|
||||
private final HashMap<Account, VersionListItem> items = new HashMap<>();
|
||||
|
||||
public LeftPaneController(AdvancedListBox leftPane) {
|
||||
this.leftPane = leftPane;
|
||||
@@ -86,6 +89,7 @@ public final class LeftPaneController {
|
||||
})))
|
||||
.add(profilePane);
|
||||
|
||||
EventBus.EVENT_BUS.channel(AccountAddedEvent.class).register(this::onAccountAdd);
|
||||
EventBus.EVENT_BUS.channel(AccountLoadingEvent.class).register(this::onAccountsLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
@@ -144,11 +148,24 @@ public final class LeftPaneController {
|
||||
else throw new Error(Launcher.i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
|
||||
private void onAccountAdd(AccountAddedEvent event) {
|
||||
Account account = event.getAccount();
|
||||
VersionListItem item = items.get(account);
|
||||
if (account instanceof YggdrasilAccount)
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
});
|
||||
}
|
||||
|
||||
private void onAccountsLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
items.clear();
|
||||
Account selectedAccount = Settings.INSTANCE.getSelectedAccount();
|
||||
for (Account account : Settings.INSTANCE.getAccounts()) {
|
||||
VersionListItem item = new VersionListItem(account.getCharacter(), accountType(account));
|
||||
items.put(account, item);
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
@@ -167,7 +184,7 @@ public final class LeftPaneController {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
} else
|
||||
item.setImage(AccountHelper.getDefaultSkin(account, 4), AccountHelper.getViewport(4));
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount)
|
||||
Accounts.getAuthlibInjectorServerNameAsync((AuthlibInjectorAccount) account)
|
||||
|
||||
@@ -23,6 +23,7 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
@@ -42,19 +43,17 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
|
||||
@@ -75,6 +74,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
private JFXSpinner spinner;
|
||||
@FXML
|
||||
private JFXMasonryPane masonryPane;
|
||||
@FXML
|
||||
private ScrollPane scrollPane;
|
||||
|
||||
{
|
||||
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
|
||||
@@ -87,7 +88,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(RefreshingVersionsEvent.class).register(event -> {
|
||||
if (event.getSource() == profile.getRepository())
|
||||
JFXUtilities.runInFXAndWait(this::loadingVersions);
|
||||
// This will occupy 0.5s. Too slow!
|
||||
JFXUtilities.runInFX(this::loadingVersions);
|
||||
});
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(event -> {
|
||||
this.profile = event.getProfile();
|
||||
@@ -103,28 +105,31 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, String game) {
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, Callable<String> gameCallable) {
|
||||
Profile profile = repository.getProfile();
|
||||
String id = version.getId();
|
||||
VersionItem item = new VersionItem();
|
||||
item.setUpdate(repository.isModpack(id));
|
||||
item.setGameVersion(game);
|
||||
Task.ofResult("game", gameCallable).subscribe(Schedulers.javafx(), vars -> {
|
||||
String game = vars.get("game");
|
||||
item.setGameVersion(game);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
libraries.append(Launcher.i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
});
|
||||
item.setVersionName(id);
|
||||
|
||||
StringBuilder libraries = new StringBuilder();
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
|
||||
libraries.append(Launcher.i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
@@ -169,7 +174,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.mismatched_type"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.invalid"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
@@ -187,7 +192,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
Launcher.i18n("modpack.export"),
|
||||
Launcher.i18n("folder.game")
|
||||
));
|
||||
versionList.setOnMouseClicked(e ->{
|
||||
versionList.setOnMouseClicked(e -> {
|
||||
versionPopup.hide();
|
||||
switch (versionList.getSelectionModel().getSelectedIndex()) {
|
||||
case 0:
|
||||
@@ -204,7 +209,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}});
|
||||
}
|
||||
});
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
@@ -221,18 +227,17 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private void loadingVersions() {
|
||||
getChildren().setAll(spinner);
|
||||
FXUtils.resetChildren(masonryPane, Collections.emptyList());
|
||||
masonryPane.getChildren().clear();
|
||||
}
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = new LinkedList<>();
|
||||
for (Version version : repository.getVersions()) {
|
||||
children.add(buildNode(repository, version, GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")));
|
||||
}
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == repository.getProfile()) {
|
||||
masonryPane.getChildren().setAll(children);
|
||||
getChildren().setAll(contentPane);
|
||||
FXUtils.resetChildren(masonryPane, children);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,9 +37,11 @@ import org.jackhuang.hmcl.util.Logging;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class ModController {
|
||||
@@ -68,21 +70,22 @@ public final class ModController {
|
||||
});
|
||||
|
||||
rootPane.setOnDragDropped(event -> {
|
||||
List<File> mods = event.getDragboard().getFiles();
|
||||
Stream<File> stream = null;
|
||||
if (mods != null)
|
||||
stream = mods.stream()
|
||||
.filter(it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)));
|
||||
if (stream != null && stream.findAny().isPresent()) {
|
||||
stream.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(versionId, it);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
||||
}
|
||||
});
|
||||
loadMods(modManager, versionId);
|
||||
event.setDropCompleted(true);
|
||||
List<File> files = event.getDragboard().getFiles();
|
||||
if (files != null) {
|
||||
Collection<File> mods = files.stream()
|
||||
.filter(it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)))
|
||||
.collect(Collectors.toList());
|
||||
if (!mods.isEmpty()) {
|
||||
mods.forEach(it -> {
|
||||
try {
|
||||
modManager.addMod(versionId, it);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
|
||||
}
|
||||
});
|
||||
loadMods(modManager, versionId);
|
||||
event.setDropCompleted(true);
|
||||
}
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@ import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.effect.BlurType;
|
||||
import javafx.scene.effect.DropShadow;
|
||||
@@ -28,10 +30,9 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
|
||||
@@ -65,8 +66,107 @@ public final class VersionItem extends StackPane {
|
||||
|
||||
private EventHandler<? super MouseEvent> launchClickedHandler = null;
|
||||
|
||||
private void initializeComponents() {
|
||||
setPickOnBounds(false);
|
||||
FXUtils.setLimitWidth(this, 160);
|
||||
FXUtils.setLimitHeight(this, 156);
|
||||
|
||||
content = new VBox();
|
||||
{
|
||||
header = new StackPane();
|
||||
VBox.setVgrow(header, Priority.ALWAYS);
|
||||
header.setPickOnBounds(false);
|
||||
header.setPadding(new Insets(8));
|
||||
header.setStyle("-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87);");
|
||||
{
|
||||
VBox headerContent = new VBox();
|
||||
headerContent.setPadding(new Insets(8, 8, 0, 8));
|
||||
{
|
||||
lblVersionName = new Label();
|
||||
lblVersionName.setStyle("-fx-font-size: 15;");
|
||||
lblVersionName.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblVersionName.setWrapText(true);
|
||||
headerContent.getChildren().add(lblVersionName);
|
||||
|
||||
lblGameVersion = new Label();
|
||||
lblGameVersion.setStyle("-fx-font-size: 11;");
|
||||
lblGameVersion.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblGameVersion.setWrapText(true);
|
||||
headerContent.getChildren().add(lblGameVersion);
|
||||
|
||||
lblLibraries = new Label();
|
||||
lblLibraries.setStyle("-fx-font-size: 10; -fx-text-fill: gray;");
|
||||
lblLibraries.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
lblLibraries.setWrapText(true);
|
||||
headerContent.getChildren().add(lblLibraries);
|
||||
}
|
||||
header.getChildren().add(headerContent);
|
||||
}
|
||||
content.getChildren().add(header);
|
||||
|
||||
body = new StackPane();
|
||||
body.setStyle("-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;");
|
||||
body.setMinHeight(40);
|
||||
body.setPickOnBounds(false);
|
||||
{
|
||||
BorderPane bodyContent = new BorderPane();
|
||||
{
|
||||
HBox hbox = new HBox();
|
||||
hbox.setSpacing(8);
|
||||
{
|
||||
btnSettings = new JFXButton();
|
||||
btnSettings.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnSettings, 30);
|
||||
FXUtils.setLimitHeight(btnSettings, 30);
|
||||
hbox.getChildren().add(btnSettings);
|
||||
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnUpdate, 30);
|
||||
FXUtils.setLimitHeight(btnUpdate, 30);
|
||||
hbox.getChildren().add(btnUpdate);
|
||||
}
|
||||
bodyContent.setLeft(hbox);
|
||||
}
|
||||
{
|
||||
HBox hbox = new HBox();
|
||||
hbox.setSpacing(8);
|
||||
{
|
||||
btnScript = new JFXButton();
|
||||
btnScript.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnScript, 30);
|
||||
FXUtils.setLimitHeight(btnScript, 30);
|
||||
hbox.getChildren().add(btnScript);
|
||||
|
||||
btnLaunch = new JFXButton();
|
||||
btnLaunch.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(btnLaunch, 30);
|
||||
FXUtils.setLimitHeight(btnLaunch, 30);
|
||||
hbox.getChildren().add(btnLaunch);
|
||||
}
|
||||
bodyContent.setRight(hbox);
|
||||
}
|
||||
body.getChildren().setAll(bodyContent);
|
||||
}
|
||||
content.getChildren().add(body);
|
||||
}
|
||||
getChildren().add(content);
|
||||
|
||||
icon = new StackPane();
|
||||
StackPane.setAlignment(icon, Pos.TOP_RIGHT);
|
||||
icon.setPickOnBounds(false);
|
||||
{
|
||||
iconView = new ImageView();
|
||||
StackPane.setAlignment(iconView, Pos.CENTER_RIGHT);
|
||||
StackPane.setMargin(iconView, new Insets(0, 12, 0, 0));
|
||||
iconView.setImage(new Image("/assets/img/icon.png"));
|
||||
icon.getChildren().add(iconView);
|
||||
}
|
||||
getChildren().add(icon);
|
||||
}
|
||||
|
||||
public VersionItem() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version-item.fxml");
|
||||
initializeComponents();
|
||||
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
|
||||
btnSettings.setGraphic(SVG.gear(Theme.blackFillBinding(), 15, 15));
|
||||
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.controls.JFXToggleButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
@@ -44,7 +43,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -84,11 +83,14 @@ public final class VersionSettingsController {
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
Task.of(variables -> variables.set("list", JavaVersion.getJREs().values().stream().map(javaVersion ->
|
||||
javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion)
|
||||
).collect(Collectors.toList()))).subscribe(Schedulers.javafx(), variables ->
|
||||
javaItem.loadChildren(variables.<Collection<Node>>get("list"))
|
||||
);
|
||||
Task.of(variables -> variables.set("list", JavaVersion.getJREs()))
|
||||
.subscribe(Schedulers.javafx(), variables -> {
|
||||
javaItem.loadChildren(
|
||||
(variables.<List<JavaVersion>>get("list")).stream()
|
||||
.map(javaVersion -> javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion))
|
||||
.collect(Collectors.toList()));
|
||||
initializeSelectedJava();
|
||||
});
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
javaItem.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe"));
|
||||
@@ -151,29 +153,17 @@ public final class VersionSettingsController {
|
||||
FXUtils.bindBoolean(chkShowLogs, versionSetting.showLogsProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
|
||||
boolean flag = false;
|
||||
Toggle defaultToggle = null;
|
||||
for (Toggle toggle : javaItem.getGroup().getToggles()) {
|
||||
if (toggle.getUserData() == Lang.invoke(versionSetting::getJavaVersion)) {
|
||||
toggle.setSelected(true);
|
||||
flag = true;
|
||||
} else if (toggle.getUserData() == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = toggle;
|
||||
}
|
||||
}
|
||||
|
||||
javaItem.setToggleSelectedListener(newValue -> {
|
||||
if (javaItem.isCustomToggle(newValue)) {
|
||||
versionSetting.setJava("Custom");
|
||||
versionSetting.setDefaultJavaPath(null);
|
||||
} else {
|
||||
versionSetting.setJava(((JavaVersion) newValue.getUserData()).getVersion());
|
||||
JavaVersion java = (JavaVersion) newValue.getUserData();
|
||||
versionSetting.setJava(java.getVersion());
|
||||
versionSetting.setDefaultJavaPath(java.getBinary().toString());
|
||||
}
|
||||
});
|
||||
|
||||
if (!flag) {
|
||||
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
|
||||
}
|
||||
|
||||
versionSetting.javaDirProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
initJavaSubtitle(versionSetting);
|
||||
@@ -207,9 +197,33 @@ public final class VersionSettingsController {
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
initializeSelectedJava();
|
||||
|
||||
loadIcon();
|
||||
}
|
||||
|
||||
private void initializeSelectedJava() {
|
||||
if (lastVersionSetting == null
|
||||
|| !JavaVersion.getJREsImmediately().isPresent() /* JREs are still being loaded */) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean flag = false;
|
||||
Toggle defaultToggle = null;
|
||||
for (Toggle toggle : javaItem.getGroup().getToggles()) {
|
||||
if (toggle.getUserData() == Lang.invoke(lastVersionSetting::getJavaVersion)) {
|
||||
toggle.setSelected(true);
|
||||
flag = true;
|
||||
} else if (toggle.getUserData() == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = toggle;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag) {
|
||||
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
|
||||
}
|
||||
}
|
||||
|
||||
private void initJavaSubtitle(VersionSetting versionSetting) {
|
||||
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
|
||||
.subscribe(Task.of(Schedulers.javafx(),
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.animation.Timeline;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public final class TransitionHandler implements AnimationHandler {
|
||||
@@ -36,6 +37,12 @@ public final class TransitionHandler implements AnimationHandler {
|
||||
public TransitionHandler(StackPane view) {
|
||||
this.view = view;
|
||||
currentNode = view.getChildren().stream().findFirst().orElse(null);
|
||||
|
||||
// prevent content overflow
|
||||
Rectangle clip = new Rectangle();
|
||||
clip.widthProperty().bind(view.widthProperty());
|
||||
clip.heightProperty().bind(view.heightProperty());
|
||||
view.setClip(clip);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
@@ -32,7 +32,6 @@ 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;
|
||||
@@ -52,7 +51,6 @@ public class MultiFileItem extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Launcher.i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Launcher.i18n("selector.choose_file"));
|
||||
private final BooleanProperty directory = new SimpleBooleanProperty(this, "directory", false);
|
||||
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||
private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
|
||||
|
||||
private final ToggleGroup group = new ToggleGroup();
|
||||
@@ -112,10 +110,6 @@ public class MultiFileItem extends ComponentList {
|
||||
if (toggleSelectedListener != null)
|
||||
toggleSelectedListener.accept(newValue);
|
||||
});
|
||||
|
||||
Tooltip tip = new Tooltip();
|
||||
tip.textProperty().bind(tooltipProperty());
|
||||
Tooltip.install(this, tip);
|
||||
}
|
||||
|
||||
public Node createChildren(String title) {
|
||||
@@ -230,16 +224,4 @@ public class MultiFileItem extends ComponentList {
|
||||
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {
|
||||
return extensionFilters;
|
||||
}
|
||||
|
||||
public String getTooltip() {
|
||||
return tooltip.get();
|
||||
}
|
||||
|
||||
public StringProperty tooltipProperty() {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
public void setTooltip(String tooltip) {
|
||||
this.tooltip.set(tooltip);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
|
||||
@@ -35,6 +35,7 @@ import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.Launcher.i18n;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public final class DownloadWizardProvider implements WizardProvider {
|
||||
private Profile profile;
|
||||
@@ -67,9 +68,9 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
if (!settings.containsKey(ModpackPage.MODPACK_FILE))
|
||||
return null;
|
||||
|
||||
File selected = Lang.get(settings, ModpackPage.MODPACK_FILE, File.class).orElse(null);
|
||||
Modpack modpack = Lang.get(settings, ModpackPage.MODPACK_CURSEFORGE_MANIFEST, Modpack.class).orElse(null);
|
||||
String name = Lang.get(settings, ModpackPage.MODPACK_NAME, String.class).orElse(null);
|
||||
File selected = tryCast(settings.get(ModpackPage.MODPACK_FILE), File.class).orElse(null);
|
||||
Modpack modpack = tryCast(settings.get(ModpackPage.MODPACK_CURSEFORGE_MANIFEST), Modpack.class).orElse(null);
|
||||
String name = tryCast(settings.get(ModpackPage.MODPACK_NAME), String.class).orElse(null);
|
||||
if (selected == null || modpack == null || name == null) return null;
|
||||
|
||||
return ModpackHelper.getInstallTask(profile, selected, name, modpack);
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.ZipEngine;
|
||||
|
||||
import java.io.File;
|
||||
@@ -83,17 +82,16 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
try (ZipEngine zip = new ZipEngine(modpackFile)) {
|
||||
Config config = Settings.INSTANCE.getRawConfig();
|
||||
|
||||
config.setHasProxy(false);
|
||||
config.setSelectedProfile("");
|
||||
config.setCommonDirectory(null);
|
||||
config.setFontFamily("Consolas");
|
||||
config.setFontSize(12);
|
||||
config.setJava(null);
|
||||
config.setLocalization(null);
|
||||
config.setAccounts(null);
|
||||
config.setSelectedAccount("");
|
||||
config.setLogLines(100);
|
||||
config.setConfigurations(null);
|
||||
config.hasProxy.set(false);
|
||||
config.selectedProfile.set("");
|
||||
config.commonDirectory.set(null);
|
||||
config.fontFamily.set("Consolas");
|
||||
config.fontSize.set(12);
|
||||
config.localization.set(null);
|
||||
config.accounts.clear();
|
||||
config.selectedAccount.set("");
|
||||
config.logLines.set(100);
|
||||
config.configurations.clear();
|
||||
|
||||
zip.putTextFile(Settings.GSON.toJson(config), Settings.SETTINGS_FILE_NAME);
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
|
||||
@@ -33,8 +33,6 @@ import org.jackhuang.hmcl.ui.construct.NoneMultipleSelectionModel;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -43,6 +41,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -154,19 +155,19 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
}
|
||||
|
||||
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
|
||||
private static final Map<String, String> TRANSLATION = Lang.mapOf(
|
||||
new Pair<>("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")),
|
||||
new Pair<>("minecraft/saves", Launcher.i18n("modpack.files.saves")),
|
||||
new Pair<>("minecraft/mods", Launcher.i18n("modpack.files.mods")),
|
||||
new Pair<>("minecraft/config", Launcher.i18n("modpack.files.config")),
|
||||
new Pair<>("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")),
|
||||
new Pair<>("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
new Pair<>("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")),
|
||||
new Pair<>("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")),
|
||||
new Pair<>("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")),
|
||||
new Pair<>("minecraft/dumps", Launcher.i18n("modpack.files.dumps")),
|
||||
new Pair<>("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")),
|
||||
new Pair<>("minecraft/scripts", Launcher.i18n("modpack.files.scripts"))
|
||||
private static final Map<String, String> TRANSLATION = mapOf(
|
||||
pair("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")),
|
||||
pair("minecraft/saves", Launcher.i18n("modpack.files.saves")),
|
||||
pair("minecraft/mods", Launcher.i18n("modpack.files.mods")),
|
||||
pair("minecraft/config", Launcher.i18n("modpack.files.config")),
|
||||
pair("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")),
|
||||
pair("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")),
|
||||
pair("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")),
|
||||
pair("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")),
|
||||
pair("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")),
|
||||
pair("minecraft/dumps", Launcher.i18n("modpack.files.dumps")),
|
||||
pair("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")),
|
||||
pair("minecraft/scripts", Launcher.i18n("modpack.files.scripts"))
|
||||
);
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -34,10 +33,8 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.*;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.jar.JarFile;
|
||||
@@ -52,29 +49,24 @@ import java.util.zip.GZIPInputStream;
|
||||
*/
|
||||
public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
private boolean launchNewerVersion(List<String> args, File jar) throws IOException, PrivilegedActionException {
|
||||
private void launchNewerVersion(List<String> args, File jar) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InvocationTargetException, IllegalAccessException {
|
||||
try (JarFile jarFile = new JarFile(jar)) {
|
||||
String mainClass = jarFile.getManifest().getMainAttributes().getValue("Main-Class");
|
||||
if (mainClass != null) {
|
||||
ArrayList<String> al = new ArrayList<>(args);
|
||||
al.add("--noupdate");
|
||||
ClassLoader pre = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
|
||||
Logging.stop();
|
||||
ClassLoader now = new URLClassLoader(new URL[]{jar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
|
||||
Thread.currentThread().setContextClassLoader(now);
|
||||
now.loadClass(mainClass).getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
|
||||
return null;
|
||||
});
|
||||
} finally {
|
||||
Logging.start();
|
||||
Thread.currentThread().setContextClassLoader(pre);
|
||||
}
|
||||
return true;
|
||||
if (mainClass == null)
|
||||
throw new ClassNotFoundException("Main-Class not found in manifest");
|
||||
ArrayList<String> al = new ArrayList<>(args);
|
||||
al.add("--noupdate");
|
||||
ClassLoader pre = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Logging.stop();
|
||||
ClassLoader now = new URLClassLoader(new URL[]{jar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
|
||||
Thread.currentThread().setContextClassLoader(now);
|
||||
now.loadClass(mainClass).getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
|
||||
} finally {
|
||||
Logging.start(Launcher.LOG_DIRECTORY);
|
||||
Thread.currentThread().setContextClassLoader(pre);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -90,15 +82,18 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
String j = m.get("loc");
|
||||
if (j != null) {
|
||||
File jar = new File(j);
|
||||
if (jar.exists() && launchNewerVersion(args, jar))
|
||||
if (jar.exists()) {
|
||||
launchNewerVersion(args, jar);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JsonParseException ex) {
|
||||
f.delete();
|
||||
} catch (IOException | PrivilegedActionException t) {
|
||||
} catch (IOException | NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException | ClassNotFoundException t) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to execute newer version application", t);
|
||||
AppDataUpgraderPackGzTask.HMCL_VER_FILE.delete(); // delete version json, let HMCL re-download the newer version.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,60 +105,57 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
checker.requestDownloadLink().then(Task.of(variables -> {
|
||||
Map<String, String> map = variables.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID);
|
||||
|
||||
if (MessageBox.confirm(Launcher.i18n("update.newest_version", version.toString()) + "\n"
|
||||
+ Launcher.i18n("update.should_open_link"),
|
||||
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION)
|
||||
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("jarsha1"))
|
||||
hash = map.get("jarsha1");
|
||||
Task task = new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else if (map != null && map.containsKey("pack") && !StringUtils.isBlank(map.get("pack")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("packsha1"))
|
||||
hash = map.get("packsha1");
|
||||
Task task = new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else {
|
||||
String url = Launcher.PUBLISH;
|
||||
if (map != null)
|
||||
if (map.containsKey(OperatingSystem.CURRENT_OS.getCheckedName()))
|
||||
url = map.get(OperatingSystem.CURRENT_OS.getCheckedName());
|
||||
else if (map.containsKey(OperatingSystem.UNKNOWN.getCheckedName()))
|
||||
url = map.get(OperatingSystem.UNKNOWN.getCheckedName());
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(url));
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e);
|
||||
OperatingSystem.setClipboard(url);
|
||||
MessageBox.show(Launcher.i18n("update.no_browser"));
|
||||
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("jarsha1"))
|
||||
hash = map.get("jarsha1");
|
||||
Task task = new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else if (map != null && map.containsKey("pack") && !StringUtils.isBlank(map.get("pack")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("packsha1"))
|
||||
hash = map.get("packsha1");
|
||||
Task task = new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else {
|
||||
String url = Launcher.PUBLISH;
|
||||
if (map != null)
|
||||
if (map.containsKey(OperatingSystem.CURRENT_OS.getCheckedName()))
|
||||
url = map.get(OperatingSystem.CURRENT_OS.getCheckedName());
|
||||
else if (map.containsKey(OperatingSystem.UNKNOWN.getCheckedName()))
|
||||
url = map.get(OperatingSystem.UNKNOWN.getCheckedName());
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(url));
|
||||
} catch (URISyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e);
|
||||
OperatingSystem.setClipboard(url);
|
||||
MessageBox.show(Launcher.i18n("update.no_browser"));
|
||||
}
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
@@ -201,7 +193,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
if (!FileUtils.makeDirectory(f.getParentFile()))
|
||||
throw new IOException("Failed to make directories: " + f.getParent());
|
||||
|
||||
for (int i = 0; f.exists(); i++)
|
||||
for (int i = 0; f.exists() && !f.delete(); i++)
|
||||
f = new File(BASE_FOLDER, "HMCL-" + newestVersion + (i > 0 ? "-" + i : "") + ".jar");
|
||||
if (!f.createNewFile())
|
||||
throw new IOException("Failed to create new file: " + f);
|
||||
@@ -219,7 +211,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
public static class AppDataUpgraderJarTask extends Task {
|
||||
|
||||
public static final File BASE_FOLDER = Launcher.getWorkingDirectory("hmcl");
|
||||
public static final File BASE_FOLDER = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json");
|
||||
|
||||
public static File getSelf(String ver) {
|
||||
|
||||
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
@@ -35,6 +34,8 @@ import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -79,7 +80,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
File file = new File(realPath);
|
||||
realPath = file.getAbsolutePath();
|
||||
try {
|
||||
realPath = java.net.URLDecoder.decode(realPath, Charsets.DEFAULT_CHARSET.name());
|
||||
realPath = java.net.URLDecoder.decode(realPath, UTF_8.name());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.OutOfDateEvent;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
@@ -30,6 +32,8 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -65,13 +69,20 @@ public final class UpdateChecker {
|
||||
*/
|
||||
public TaskResult<VersionNumber> process(final boolean showMessage) {
|
||||
return new TaskResult<VersionNumber>() {
|
||||
GetTask http = new GetTask(NetworkUtils.toURL(Launcher.UPDATE_SERVER + "/hmcl/update.php?version=" + Launcher.VERSION));
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return value == null ? Collections.singleton(http) : Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (Launcher.VERSION.contains("@"))
|
||||
return;
|
||||
|
||||
if (value == null) {
|
||||
versionString = NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update.php?version=" + Launcher.VERSION));
|
||||
versionString = http.getResult();
|
||||
value = VersionNumber.asVersion(versionString);
|
||||
}
|
||||
|
||||
@@ -115,7 +126,7 @@ public final class UpdateChecker {
|
||||
public void execute() {
|
||||
if (download_link == null)
|
||||
try {
|
||||
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update_link.php")), Map.class);
|
||||
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL(Launcher.UPDATE_SERVER + "/hmcl/update_link.php")), Map.class);
|
||||
} catch (JsonSyntaxException | IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to get update link.", e);
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
|
||||
Logging.LOG.log(Level.SEVERE, text);
|
||||
|
||||
if (checkThrowable(e) && !text.contains("OpenJDK")) {
|
||||
if (checkThrowable(e)) {
|
||||
Platform.runLater(() -> new CrashWindow(text).show());
|
||||
if (!Launcher.UPDATE_CHECKER.isOutOfDate())
|
||||
reportToServer(text);
|
||||
|
||||
@@ -911,6 +911,14 @@
|
||||
-fx-stroke-width: 5.0;
|
||||
}
|
||||
|
||||
.small-spinner {
|
||||
-jfx-radius: 10;
|
||||
}
|
||||
|
||||
.small-spinner > .arc {
|
||||
-fx-stroke-width: 3.0;
|
||||
}
|
||||
|
||||
.second-spinner {
|
||||
-jfx-radius: 30;
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
|
||||
<JFXComboBox fx:id="cboType" GridPane.columnIndex="1" GridPane.rowIndex="0" GridPane.columnSpan="2"/>
|
||||
|
||||
<Label fx:id="lblAddInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT"
|
||||
<Label fx:id="lblInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT"
|
||||
GridPane.columnIndex="0" GridPane.rowIndex="1"/>
|
||||
|
||||
<JFXComboBox fx:id="cboServers" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||
|
||||
<Hyperlink fx:id="linkAddInjectorServer" text="%account.injector.add"
|
||||
onMouseClicked="#onAddInjecterServer" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
|
||||
<Hyperlink fx:id="linkManageInjectorServers" text="%account.injector.manage"
|
||||
onMouseClicked="#onManageInjecterServers" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
|
||||
|
||||
<Label text="%account.username" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
|
||||
|
||||
@@ -56,9 +56,11 @@
|
||||
</body>
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning"/>
|
||||
<JFXButton fx:id="btnAccept" onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
|
||||
<StackPane fx:id="acceptPane">
|
||||
<JFXButton fx:id="btnAccept" onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
|
||||
<JFXSpinner fx:id="spinnerAccept" styleClass="small-spinner" />
|
||||
</StackPane>
|
||||
<JFXButton onMouseClicked="#onCreationCancel" text="%button.cancel" styleClass="dialog-cancel"/>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER"/>
|
||||
</fx:root>
|
||||
@@ -12,7 +12,7 @@
|
||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<ScrollPane fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane" pickOnBounds="false" FXUtils.limitWidth="160" FXUtils.limitHeight="156">
|
||||
<VBox fx:id="content" pickOnBounds="false">
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS" pickOnBounds="false" style="-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;">
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblGameVersion" style="-fx-font-size: 11;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblLibraries" style="-fx-font-size: 10; -fx-text-fill: gray;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40" pickOnBounds="false">
|
||||
<BorderPane>
|
||||
<left>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnSettings" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnUpdate" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnScript" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnLaunch" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minHeight="30" minWidth="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
</StackPane>
|
||||
</VBox>
|
||||
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT" pickOnBounds="false">
|
||||
<ImageView fx:id="iconView" StackPane.alignment="CENTER_RIGHT">
|
||||
<StackPane.margin>
|
||||
<Insets right="12" />
|
||||
</StackPane.margin>
|
||||
<Image url="/assets/img/icon.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -18,7 +18,7 @@
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=Author
|
||||
about.author.statement=huanghongxun (MCF: klkl6523)
|
||||
about.author.statement=huanghongxun ([email protected])
|
||||
about.thanks_to=Thanks to
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
|
||||
about.dependency=Dependency
|
||||
@@ -39,7 +39,8 @@ account.failed.invalid_credentials=Incorrect password. Or forbidden to log in te
|
||||
account.failed.invalid_password=Invalid password
|
||||
account.failed.invalid_token=Please log out and re-input your password to log in.
|
||||
account.failed.no_charactor=No character in this account.
|
||||
account.injector.add=Add new authentication server
|
||||
account.injector.add=Add authentication server
|
||||
account.injector.manage=Manage authentication servers
|
||||
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
|
||||
account.injector.server=Auth Server
|
||||
account.injector.server_ip=Server URL
|
||||
@@ -106,7 +107,6 @@ crash.advice.no=No advice.
|
||||
crash.advice.no_lwjgl=Maybe drivers caused problems.
|
||||
crash.advice.otherwise=Maybe mods caused problems.
|
||||
crash.error=Minecraft has crashed.
|
||||
crash.headless=If your OS is Linux, please use Oracle JDK instead of OpenJDK, or add "-Djava.awt.headless=false" JVM argument, or check if your Xserver works normally.
|
||||
crash.launcher=Launcher has crashed!
|
||||
crash.minecraft=Minecraft has crashed!
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
|
||||
@@ -198,7 +198,6 @@ launcher.crash=Hello Minecraft! Launcher has crashed!
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
|
||||
launcher.exit_failed=Failed to shutdown.
|
||||
launcher.modpack=Documentations for modpacks.
|
||||
launcher.open_jdk=We have found that you started this application using OpenJDK, which will cause so many troubles drawing the UI. We suggest you using Oracle JDK instead.
|
||||
launcher.restart=Options will be in operations only if restart this app.
|
||||
launcher.tab.about=About
|
||||
launcher.tab.general=General
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
about.copyright=版权
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (百度贴吧:huanghongxun20)
|
||||
about.author.statement=huanghongxun ([email protected])
|
||||
about.thanks_to=鸣谢
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
|
||||
about.dependency=依赖
|
||||
@@ -39,7 +39,8 @@ account.failed.invalid_credentials=您的用户名或密码错误,或者登录
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.no_charactor=该帐号没有角色
|
||||
account.injector.add=添加登录认证服务器
|
||||
account.injector.add=添加认证服务器
|
||||
account.injector.manage=管理认证服务器
|
||||
account.injector.http=警告:此服务器使用不安全的http协议,您的密码在登录时会被明文传输。
|
||||
account.injector.server=认证服务器
|
||||
account.injector.server_ip=服务器地址
|
||||
@@ -106,7 +107,6 @@ crash.advice.no=无建议。
|
||||
crash.advice.no_lwjgl=可能是游戏依赖库不完整或解压依赖库时出错。可以通过下载整合包解决问题。
|
||||
crash.advice.otherwise=可能是Mod或其他问题。
|
||||
crash.error=您的Minecraft崩溃了。
|
||||
crash.headless=如果您的操作系统是Linux,请注意不要使用OpenJDK,务必使用Oracle JDK,或尝试添加-Djava.awt.headless=false参数,或检查您的Xserver是否正常
|
||||
crash.launcher=启动器崩溃了!
|
||||
crash.minecraft=Minecraft崩溃了!请认真阅读建议。
|
||||
crash.user_fault=您的系统或Java环境可能安装不当导致本软件崩溃,请检查您的Java环境或您的电脑!可以尝试重新安装Java。
|
||||
@@ -198,7 +198,6 @@ launcher.crash=Hello Minecraft!遇到了无法处理的错误,请复制下列
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
launcher.exit_failed=强制退出失败,可能是Forge 1.7.10及更高版本导致的,无法解决。
|
||||
launcher.modpack=整合包作者帮助
|
||||
launcher.open_jdk=我们发现您正在使用OpenJDK,这会导致很多界面问题,我们建议您更换Oracle JDK。
|
||||
launcher.restart=本界面选项需要重启本启动器生效
|
||||
launcher.tab.about=关于
|
||||
launcher.tab.general=通用
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.auth;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -29,39 +30,38 @@ import java.util.UUID;
|
||||
public abstract class Account {
|
||||
|
||||
/**
|
||||
* @return the account name (mostly email)
|
||||
* @return the name of the account who owns the character
|
||||
*/
|
||||
public abstract String getUsername();
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the character name
|
||||
*/
|
||||
public abstract String getCharacter();
|
||||
|
||||
/**
|
||||
* @return the UUID
|
||||
* @return the character UUID
|
||||
*/
|
||||
public abstract UUID getUUID();
|
||||
|
||||
/**
|
||||
* log in.
|
||||
* @return the specific player's info.
|
||||
* @throws AuthenticationException if server error occurred.
|
||||
* Login with stored credentials.
|
||||
*
|
||||
* @throws ServerDisconnectException if an network error has occurred, in which case password login won't be tried.
|
||||
* @throws AuthenticationException if an error has occurred. If it's not a {@link ServerDisconnectException}, password login will be tried.
|
||||
*/
|
||||
public abstract AuthInfo logIn() throws AuthenticationException;
|
||||
public abstract AuthInfo logIn() throws ServerDisconnectException, AuthenticationException;
|
||||
|
||||
/**
|
||||
* Login with specified password.
|
||||
*/
|
||||
public abstract AuthInfo logInWithPassword(String password) throws AuthenticationException;
|
||||
|
||||
public abstract boolean canPlayOffline();
|
||||
|
||||
/**
|
||||
* Play offline.
|
||||
* @return the specific offline player's info.
|
||||
*/
|
||||
public abstract AuthInfo playOffline();
|
||||
|
||||
public abstract void logOut();
|
||||
public abstract Optional<AuthInfo> playOffline();
|
||||
|
||||
public abstract Map<Object, Object> toStorage();
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ package org.jackhuang.hmcl.auth;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -28,29 +30,19 @@ import org.jackhuang.hmcl.util.Immutable;
|
||||
public final class AuthInfo {
|
||||
|
||||
private final String username;
|
||||
private final String userId;
|
||||
private final UUID uuid;
|
||||
private final String accessToken;
|
||||
private final UserType userType;
|
||||
private final String userProperties;
|
||||
private final Arguments arguments;
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken) {
|
||||
this(username, userId, accessToken, UserType.LEGACY);
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties) {
|
||||
this(username, uuid, accessToken, userProperties, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken, UserType userType) {
|
||||
this(username, userId, accessToken, userType, "{}");
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties) {
|
||||
this(username, userId, accessToken, userType, userProperties, null);
|
||||
}
|
||||
|
||||
public AuthInfo(String username, String userId, String accessToken, UserType userType, String userProperties, Arguments arguments) {
|
||||
public AuthInfo(String username, UUID uuid, String accessToken, String userProperties, Arguments arguments) {
|
||||
this.username = username;
|
||||
this.userId = userId;
|
||||
this.uuid = uuid;
|
||||
this.accessToken = accessToken;
|
||||
this.userType = userType;
|
||||
this.userProperties = userProperties;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
@@ -59,18 +51,14 @@ public final class AuthInfo {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
public UUID getUUID() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public UserType getUserType() {
|
||||
return userType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties of this user.
|
||||
* Don't know the difference between user properties and user property map.
|
||||
@@ -81,11 +69,14 @@ public final class AuthInfo {
|
||||
return userProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null if no argument is specified
|
||||
*/
|
||||
public Arguments getArguments() {
|
||||
return arguments;
|
||||
}
|
||||
|
||||
public AuthInfo setArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, userId, accessToken, userType, userProperties, arguments);
|
||||
public AuthInfo withArguments(Arguments arguments) {
|
||||
return new AuthInfo(username, uuid, accessToken, userProperties, arguments);
|
||||
}
|
||||
}
|
||||
@@ -20,23 +20,24 @@ package org.jackhuang.hmcl.auth;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Select character by name.
|
||||
*/
|
||||
public class SpecificCharacterSelector implements CharacterSelector {
|
||||
private final String id;
|
||||
private UUID uuid;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param id character's name.
|
||||
* @param uuid character's uuid.
|
||||
*/
|
||||
public SpecificCharacterSelector(String id) {
|
||||
this.id = id;
|
||||
public SpecificCharacterSelector(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException {
|
||||
return names.stream().filter(profile -> profile.getName().equals(id)).findAny().orElseThrow(() -> new NoSelectedCharacterException(account));
|
||||
return names.stream().filter(profile -> profile.getId().equals(uuid)).findAny().orElseThrow(() -> new NoSelectedCharacterException(account));
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[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.auth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public enum UserType {
|
||||
LEGACY,
|
||||
MOJANG;
|
||||
|
||||
public static UserType fromName(String name) {
|
||||
return BY_NAME.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public static UserType fromLegacy(boolean isLegacy) {
|
||||
return isLegacy ? LEGACY : MOJANG;
|
||||
}
|
||||
|
||||
static {
|
||||
HashMap<String, UserType> byName = new HashMap<>();
|
||||
for (UserType type : values())
|
||||
byName.put(type.name().toLowerCase(), type);
|
||||
BY_NAME = Collections.unmodifiableMap(byName);
|
||||
}
|
||||
|
||||
public static final Map<String, UserType> BY_NAME;
|
||||
|
||||
}
|
||||
+8
-7
@@ -25,21 +25,23 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
private final String serverBaseURL;
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, String clientToken, String character, YggdrasilSession session) {
|
||||
super(service, username, clientToken, character, session);
|
||||
protected AuthlibInjectorAccount(YggdrasilService service, String serverBaseURL, ExceptionalSupplier<String, ?> injectorJarPath, String username, UUID characterUUID, YggdrasilSession session) {
|
||||
super(service, username, characterUUID, session);
|
||||
|
||||
this.injectorJarPath = injectorJarPath;
|
||||
this.serverBaseURL = serverBaseURL;
|
||||
@@ -65,13 +67,12 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
try {
|
||||
thread.join();
|
||||
|
||||
String arg = "-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL;
|
||||
Arguments arguments = Arguments.addJVMArguments(null, arg);
|
||||
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL);
|
||||
|
||||
if (flag.get())
|
||||
arguments = Arguments.addJVMArguments(arguments, "-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), Charsets.UTF_8));
|
||||
arguments = arguments.addJVMArguments("-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), UTF_8));
|
||||
|
||||
return info.setArguments(arguments);
|
||||
return info.withArguments(arguments);
|
||||
} catch (Exception e) {
|
||||
throw new AuthenticationException("Unable to get authlib injector jar path", e);
|
||||
}
|
||||
|
||||
+8
-11
@@ -6,14 +6,13 @@ import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
|
||||
@@ -33,7 +32,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
throw new IllegalArgumentException("Additional data should be server base url string for authlib injector accounts.");
|
||||
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider((String) serverBaseURL), proxy),
|
||||
(String) serverBaseURL, injectorJarPathSupplier, username, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), null, null);
|
||||
(String) serverBaseURL, injectorJarPathSupplier, username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
@@ -43,16 +42,14 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
Objects.requireNonNull(storage);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
String username = Lang.get(storage, "username", String.class)
|
||||
YggdrasilSession session = YggdrasilSession.fromStorage(storage);
|
||||
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||
String clientToken = Lang.get(storage, "clientToken", String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have client token."));
|
||||
String character = Lang.get(storage, "clientToken", String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name."));
|
||||
String apiRoot = Lang.get(storage, "serverBaseURL", String.class)
|
||||
String apiRoot = tryCast(storage.get("serverBaseURL"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have API root."));
|
||||
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(apiRoot), proxy),
|
||||
apiRoot, injectorJarPathSupplier, username, clientToken, character, YggdrasilSession.fromStorage(storage));
|
||||
apiRoot, injectorJarPathSupplier, username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
}
|
||||
@@ -20,12 +20,18 @@ package org.jackhuang.hmcl.auth.offline;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
@@ -33,9 +39,9 @@ import java.util.UUID;
|
||||
public class OfflineAccount extends Account {
|
||||
|
||||
private final String username;
|
||||
private final String uuid;
|
||||
private final UUID uuid;
|
||||
|
||||
OfflineAccount(String username, String uuid) {
|
||||
OfflineAccount(String username, UUID uuid) {
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
@@ -48,7 +54,7 @@ public class OfflineAccount extends Account {
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return UUIDTypeAdapter.fromString(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,7 +72,7 @@ public class OfflineAccount extends Account {
|
||||
if (StringUtils.isBlank(username))
|
||||
throw new AuthenticationException("Username cannot be empty");
|
||||
|
||||
return new AuthInfo(username, uuid, uuid);
|
||||
return new AuthInfo(username, uuid, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), "{}");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,25 +81,15 @@ public class OfflineAccount extends Account {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
// Offline account need not log out.
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlayOffline() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthInfo playOffline() {
|
||||
throw new IllegalStateException();
|
||||
public Optional<AuthInfo> playOffline() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> toStorage() {
|
||||
return Lang.mapOf(
|
||||
new Pair<>("uuid", uuid),
|
||||
new Pair<>("username", username)
|
||||
return mapOf(
|
||||
pair("uuid", UUIDTypeAdapter.fromUUID(uuid)),
|
||||
pair("username", username)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,15 +19,14 @@ package org.jackhuang.hmcl.auth.offline;
|
||||
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -46,19 +45,17 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
|
||||
@Override
|
||||
public OfflineAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
String username = Lang.get(storage, "username", String.class)
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalStateException("Offline account configuration malformed."));
|
||||
String uuid = Lang.get(storage, "uuid", String.class)
|
||||
UUID uuid = tryCast(storage.get("uuid"), String.class)
|
||||
.map(UUIDTypeAdapter::fromString)
|
||||
.orElse(getUUIDFromUserName(username));
|
||||
|
||||
// Check if the uuid is vaild
|
||||
UUIDTypeAdapter.fromString(uuid);
|
||||
|
||||
return new OfflineAccount(username, uuid);
|
||||
}
|
||||
|
||||
private static String getUUIDFromUserName(String username) {
|
||||
return DigestUtils.md5Hex(username);
|
||||
private static UUID getUUIDFromUserName(String username) {
|
||||
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(UTF_8));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
final class AuthenticationResponse extends ErrorResponse {
|
||||
|
||||
private final String accessToken;
|
||||
private final String clientToken;
|
||||
private final GameProfile selectedProfile;
|
||||
private final GameProfile[] availableProfiles;
|
||||
private final User user;
|
||||
|
||||
public AuthenticationResponse() {
|
||||
this(null, null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
public AuthenticationResponse(String accessToken, String clientToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user, String error, String errorMessage, String cause) {
|
||||
super(error, errorMessage, cause);
|
||||
|
||||
this.accessToken = accessToken;
|
||||
this.clientToken = clientToken;
|
||||
this.selectedProfile = selectedProfile;
|
||||
this.availableProfiles = availableProfiles;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return clientToken;
|
||||
}
|
||||
|
||||
public GameProfile getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return availableProfiles;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
class ErrorResponse {
|
||||
private final String error;
|
||||
private final String errorMessage;
|
||||
private final String cause;
|
||||
|
||||
public ErrorResponse() {
|
||||
this(null, null, null);
|
||||
}
|
||||
|
||||
public ErrorResponse(String error, String errorMessage, String cause) {
|
||||
this.error = error;
|
||||
this.errorMessage = errorMessage;
|
||||
this.cause = cause;
|
||||
}
|
||||
|
||||
public String getError() {
|
||||
return error;
|
||||
}
|
||||
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
public String getCause() {
|
||||
return cause;
|
||||
}
|
||||
}
|
||||
@@ -17,11 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import com.google.gson.*;
|
||||
import org.jackhuang.hmcl.auth.UserType;
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -34,7 +31,6 @@ public final class GameProfile {
|
||||
private final UUID id;
|
||||
private final String name;
|
||||
private final PropertyMap properties;
|
||||
private final boolean legacy;
|
||||
|
||||
public GameProfile() {
|
||||
this(null, null);
|
||||
@@ -45,14 +41,9 @@ public final class GameProfile {
|
||||
}
|
||||
|
||||
public GameProfile(UUID id, String name, PropertyMap properties) {
|
||||
this(id, name, properties, false);
|
||||
}
|
||||
|
||||
public GameProfile(UUID id, String name, PropertyMap properties, boolean legacy) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.properties = properties;
|
||||
this.legacy = legacy;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
@@ -63,46 +54,11 @@ public final class GameProfile {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return nullable
|
||||
*/
|
||||
public PropertyMap getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
public boolean isLegacy() {
|
||||
return legacy;
|
||||
}
|
||||
|
||||
public UserType getUserType() {
|
||||
return UserType.fromLegacy(isLegacy());
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
|
||||
|
||||
public static final Serializer INSTANCE = new Serializer();
|
||||
|
||||
private Serializer() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(GameProfile src, Type type, JsonSerializationContext context) {
|
||||
JsonObject result = new JsonObject();
|
||||
if (src.getId() != null)
|
||||
result.add("id", context.serialize(src.getId()));
|
||||
if (src.getName() != null)
|
||||
result.addProperty("name", src.getName());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameProfile deserialize(JsonElement je, Type type, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (!(je instanceof JsonObject))
|
||||
throw new JsonParseException("The json element is not a JsonObject.");
|
||||
|
||||
JsonObject json = (JsonObject) je;
|
||||
|
||||
UUID id = json.has("id") ? context.deserialize(json.get("id"), UUID.class) : null;
|
||||
String name = json.has("name") ? json.getAsJsonPrimitive("name").getAsString() : null;
|
||||
return new GameProfile(id, name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.util.Immutable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Immutable
|
||||
public final class TextureResponse {
|
||||
private final UUID profileId;
|
||||
private final String profileName;
|
||||
private final Map<TextureType, Texture> textures;
|
||||
|
||||
public TextureResponse() {
|
||||
this(UUID.randomUUID(), "", Collections.emptyMap());
|
||||
}
|
||||
|
||||
public TextureResponse(UUID profileId, String profileName, Map<TextureType, Texture> textures) {
|
||||
this.profileId = profileId;
|
||||
this.profileName = profileName;
|
||||
this.textures = textures;
|
||||
}
|
||||
|
||||
public UUID getProfileId() {
|
||||
return profileId;
|
||||
}
|
||||
|
||||
public String getProfileName() {
|
||||
return profileName;
|
||||
}
|
||||
|
||||
public Map<TextureType, Texture> getTextures() {
|
||||
return textures == null ? null : Collections.unmodifiableMap(textures);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
@@ -34,15 +33,13 @@ public class YggdrasilAccount extends Account {
|
||||
private final YggdrasilService service;
|
||||
private boolean isOnline = false;
|
||||
private YggdrasilSession session;
|
||||
private final String clientToken;
|
||||
private String character;
|
||||
private UUID characterUUID;
|
||||
|
||||
protected YggdrasilAccount(YggdrasilService service, String username, String clientToken, String character, YggdrasilSession session) {
|
||||
protected YggdrasilAccount(YggdrasilService service, String username, UUID characterUUID, YggdrasilSession session) {
|
||||
this.service = service;
|
||||
this.username = username;
|
||||
this.session = session;
|
||||
this.clientToken = clientToken;
|
||||
this.character = character;
|
||||
this.characterUUID = characterUUID;
|
||||
|
||||
if (session == null || session.getSelectedProfile() == null || StringUtils.isBlank(session.getAccessToken()))
|
||||
this.session = null;
|
||||
@@ -70,20 +67,20 @@ public class YggdrasilAccount extends Account {
|
||||
public AuthInfo logIn() throws AuthenticationException {
|
||||
if (!canPlayOnline()) {
|
||||
logInWithToken();
|
||||
selectProfile(new SpecificCharacterSelector(character));
|
||||
selectProfile(new SpecificCharacterSelector(characterUUID));
|
||||
}
|
||||
return toAuthInfo();
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final AuthInfo logInWithPassword(String password) throws AuthenticationException {
|
||||
return logInWithPassword(password, new SpecificCharacterSelector(character));
|
||||
return logInWithPassword(password, new SpecificCharacterSelector(characterUUID));
|
||||
}
|
||||
|
||||
protected AuthInfo logInWithPassword(String password, CharacterSelector selector) throws AuthenticationException {
|
||||
session = service.authenticate(username, password, clientToken);
|
||||
session = service.authenticate(username, password, UUIDTypeAdapter.fromUUID(UUID.randomUUID()));
|
||||
selectProfile(selector);
|
||||
return toAuthInfo();
|
||||
return session.toAuthInfo();
|
||||
}
|
||||
|
||||
private void selectProfile(CharacterSelector selector) throws AuthenticationException {
|
||||
@@ -91,61 +88,42 @@ public class YggdrasilAccount extends Account {
|
||||
if (session.getAvailableProfiles() == null || session.getAvailableProfiles().length <= 0)
|
||||
throw new NoCharacterException(this);
|
||||
|
||||
session.setSelectedProfile(selector.select(this, Arrays.asList(session.getAvailableProfiles())));
|
||||
session = service.refresh(session.getAccessToken(), session.getClientToken(), selector.select(this, Arrays.asList(session.getAvailableProfiles())));
|
||||
}
|
||||
|
||||
character = session.getSelectedProfile().getName();
|
||||
characterUUID = session.getSelectedProfile().getId();
|
||||
}
|
||||
|
||||
private void logInWithToken() throws AuthenticationException {
|
||||
if (service.validate(session.getAccessToken(), clientToken)) {
|
||||
if (service.validate(session.getAccessToken(), session.getClientToken())) {
|
||||
isOnline = true;
|
||||
return;
|
||||
}
|
||||
session = service.refresh(session.getAccessToken(), clientToken);
|
||||
}
|
||||
|
||||
private AuthInfo toAuthInfo() {
|
||||
GameProfile profile = session.getSelectedProfile();
|
||||
|
||||
return new AuthInfo(profile.getName(), UUIDTypeAdapter.fromUUID(profile.getId()), session.getAccessToken(), profile.getUserType(),
|
||||
new GsonBuilder().registerTypeAdapter(PropertyMap.class, PropertyMap.LegacySerializer.INSTANCE).create().toJson(Optional.ofNullable(session.getUser()).map(User::getProperties).orElseGet(PropertyMap::new)));
|
||||
session = service.refresh(session.getAccessToken(), session.getClientToken(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPlayOffline() {
|
||||
return isLoggedIn() && session.getSelectedProfile() != null && !canPlayOnline();
|
||||
}
|
||||
public Optional<AuthInfo> playOffline() {
|
||||
if (isLoggedIn() && session.getSelectedProfile() != null && !canPlayOnline())
|
||||
return Optional.of(session.toAuthInfo());
|
||||
|
||||
@Override
|
||||
public AuthInfo playOffline() {
|
||||
if (!canPlayOffline())
|
||||
throw new IllegalStateException("Current account " + this + " cannot play offline.");
|
||||
|
||||
return toAuthInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logOut() {
|
||||
isOnline = false;
|
||||
session = null;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Object, Object> toStorage() {
|
||||
if (session == null)
|
||||
throw new IllegalStateException("No session is specified");
|
||||
|
||||
HashMap<Object, Object> storage = new HashMap<>();
|
||||
|
||||
storage.put("username", getUsername());
|
||||
storage.put("clientToken", clientToken);
|
||||
storage.put("character", character);
|
||||
if (session != null)
|
||||
storage.putAll(session.toStorage());
|
||||
|
||||
storage.putAll(session.toStorage());
|
||||
return storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
if (session == null)
|
||||
if (session == null || session.getSelectedProfile() == null)
|
||||
return null;
|
||||
else
|
||||
return session.getSelectedProfile().getId();
|
||||
@@ -157,7 +135,7 @@ public class YggdrasilAccount extends Account {
|
||||
|
||||
public Optional<Texture> getSkin(GameProfile profile) throws AuthenticationException {
|
||||
if (!service.getTextures(profile).isPresent()) {
|
||||
session.setAvailableProfile(profile = service.getCompleteGameProfile(profile.getId()));
|
||||
profile = service.getCompleteGameProfile(profile.getId()).orElse(profile);
|
||||
}
|
||||
|
||||
return service.getTextures(profile).map(map -> map.get(TextureType.SKIN));
|
||||
|
||||
+8
-9
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
@@ -28,6 +27,8 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -47,7 +48,7 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
Objects.requireNonNull(password);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, UUIDTypeAdapter.fromUUID(UUID.randomUUID()), null, null);
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
@@ -57,14 +58,12 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
Objects.requireNonNull(storage);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
String username = Lang.get(storage, "username", String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||
String clientToken = Lang.get(storage, "clientToken", String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have client token."));
|
||||
String character = Lang.get(storage, "clientToken", String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have selected character name."));
|
||||
YggdrasilSession session = YggdrasilSession.fromStorage(storage);
|
||||
|
||||
return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, clientToken, character, YggdrasilSession.fromStorage(storage));
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalArgumentException("storage does not have username"));
|
||||
|
||||
return new YggdrasilAccount(new YggdrasilService(provider, proxy), username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
|
||||
public static String randomToken() {
|
||||
|
||||
@@ -4,13 +4,20 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Lang.liftFunction;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public class YggdrasilService {
|
||||
|
||||
private final YggdrasilProvider provider;
|
||||
@@ -31,23 +38,39 @@ public class YggdrasilService {
|
||||
Objects.requireNonNull(clientToken);
|
||||
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("agent", Lang.mapOf(
|
||||
new Pair<>("name", "Minecraft"),
|
||||
new Pair<>("version", 1)
|
||||
request.put("agent", mapOf(
|
||||
pair("name", "Minecraft"),
|
||||
pair("version", 1)
|
||||
));
|
||||
request.put("username", username);
|
||||
request.put("password", password);
|
||||
request.put("clientToken", clientToken);
|
||||
request.put("requestUser", true);
|
||||
|
||||
return handle(request(provider.getAuthenticationURL(), request), clientToken);
|
||||
return handleAuthenticationResponse(request(provider.getAuthenticationURL(), request), clientToken);
|
||||
}
|
||||
|
||||
public YggdrasilSession refresh(String accessToken, String clientToken) throws AuthenticationException {
|
||||
private Map<String, Object> createRequestWithCredentials(String accessToken, String clientToken) {
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("accessToken", accessToken);
|
||||
request.put("clientToken", clientToken);
|
||||
return request;
|
||||
}
|
||||
|
||||
public YggdrasilSession refresh(String accessToken, String clientToken, GameProfile characterToSelect) throws AuthenticationException {
|
||||
Objects.requireNonNull(accessToken);
|
||||
Objects.requireNonNull(clientToken);
|
||||
|
||||
return handle(request(provider.getRefreshmentURL(), new RefreshRequest(accessToken, clientToken)), clientToken);
|
||||
Map<String, Object> request = createRequestWithCredentials(accessToken, clientToken);
|
||||
request.put("requestUser", true);
|
||||
|
||||
if (characterToSelect != null) {
|
||||
request.put("selectedProfile", mapOf(
|
||||
pair("id", characterToSelect.getId()),
|
||||
pair("name", characterToSelect.getName())));
|
||||
}
|
||||
|
||||
return handleAuthenticationResponse(request(provider.getRefreshmentURL(), request), clientToken);
|
||||
}
|
||||
|
||||
public boolean validate(String accessToken) throws AuthenticationException {
|
||||
@@ -58,7 +81,7 @@ public class YggdrasilService {
|
||||
Objects.requireNonNull(accessToken);
|
||||
|
||||
try {
|
||||
requireEmpty(request(provider.getValidationURL(), new RefreshRequest(accessToken, clientToken)));
|
||||
requireEmpty(request(provider.getValidationURL(), createRequestWithCredentials(accessToken, clientToken)));
|
||||
return true;
|
||||
} catch (InvalidCredentialsException | InvalidTokenException e) {
|
||||
return false;
|
||||
@@ -72,7 +95,7 @@ public class YggdrasilService {
|
||||
public void invalidate(String accessToken, String clientToken) throws AuthenticationException {
|
||||
Objects.requireNonNull(accessToken);
|
||||
|
||||
requireEmpty(request(provider.getInvalidationURL(), GSON.toJson(new RefreshRequest(accessToken, clientToken))));
|
||||
requireEmpty(request(provider.getInvalidationURL(), createRequestWithCredentials(accessToken, clientToken)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,49 +103,33 @@ public class YggdrasilService {
|
||||
*
|
||||
* Game profile provided from authentication is not complete (no skin data in properties).
|
||||
*
|
||||
* @param userId the userId that the character corresponding to.
|
||||
* @return the complete game profile(filled with more properties), null if character corresponding to {@code userId} does not exist.
|
||||
* @throws AuthenticationException if an I/O error occurred or server response malformed.
|
||||
* @param uuid the uuid that the character corresponding to.
|
||||
* @return the complete game profile(filled with more properties)
|
||||
*/
|
||||
public GameProfile getCompleteGameProfile(UUID userId) throws AuthenticationException {
|
||||
Objects.requireNonNull(userId);
|
||||
public Optional<GameProfile> getCompleteGameProfile(UUID uuid) throws AuthenticationException {
|
||||
Objects.requireNonNull(uuid);
|
||||
|
||||
ProfileResponse response = fromJson(request(provider.getProfilePropertiesURL(userId), null), ProfileResponse.class);
|
||||
if (response == null)
|
||||
return null;
|
||||
|
||||
return new GameProfile(response.getId(), response.getName(), response.getProperties());
|
||||
return Optional.ofNullable(fromJson(request(provider.getProfilePropertiesURL(uuid), null), GameProfile.class));
|
||||
}
|
||||
|
||||
public Optional<Map<TextureType, Texture>> getTextures(GameProfile profile) throws AuthenticationException {
|
||||
Objects.requireNonNull(profile);
|
||||
|
||||
return Optional.ofNullable(profile.getProperties())
|
||||
.map(properties -> properties.get("textures"))
|
||||
.map(encodedTextures -> new String(Base64.getDecoder().decode(encodedTextures), Charsets.UTF_8))
|
||||
.map(Lang.liftFunction(textures -> fromJson(textures, TextureResponse.class)))
|
||||
.map(TextureResponse::getTextures);
|
||||
.flatMap(properties -> Optional.ofNullable(properties.get("textures")))
|
||||
.map(encodedTextures -> new String(Base64.getDecoder().decode(encodedTextures), UTF_8))
|
||||
.map(liftFunction(textures -> fromJson(textures, TextureResponse.class)))
|
||||
.flatMap(response -> Optional.ofNullable(response.textures));
|
||||
}
|
||||
|
||||
private String request(URL url, Object input) throws AuthenticationException {
|
||||
try {
|
||||
if (input == null)
|
||||
return NetworkUtils.doGet(url, proxy);
|
||||
else
|
||||
return NetworkUtils.doPost(url, input instanceof String ? (String) input : GSON.toJson(input), "application/json");
|
||||
} catch (IOException e) {
|
||||
throw new ServerDisconnectException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static YggdrasilSession handle(String responseText, String clientToken) throws AuthenticationException {
|
||||
private static YggdrasilSession handleAuthenticationResponse(String responseText, String clientToken) throws AuthenticationException {
|
||||
AuthenticationResponse response = fromJson(responseText, AuthenticationResponse.class);
|
||||
handleErrorMessage(response);
|
||||
|
||||
if (!clientToken.equals(response.getClientToken()))
|
||||
throw new AuthenticationException("Client token changed from " + response.getClientToken() + " to " + clientToken);
|
||||
if (!clientToken.equals(response.clientToken))
|
||||
throw new AuthenticationException("Client token changed from " + clientToken + " to " + response.clientToken);
|
||||
|
||||
return new YggdrasilSession(response.getAccessToken(), response.getSelectedProfile(), response.getAvailableProfiles(), response.getUser());
|
||||
return new YggdrasilSession(response.clientToken, response.accessToken, response.selectedProfile, response.availableProfiles, response.user);
|
||||
}
|
||||
|
||||
private static void requireEmpty(String response) throws AuthenticationException {
|
||||
@@ -137,15 +144,30 @@ public class YggdrasilService {
|
||||
}
|
||||
|
||||
private static void handleErrorMessage(ErrorResponse response) throws AuthenticationException {
|
||||
if (!StringUtils.isBlank(response.getError())) {
|
||||
if (response.getErrorMessage() != null)
|
||||
if (response.getErrorMessage().contains("Invalid credentials"))
|
||||
if (!StringUtils.isBlank(response.error)) {
|
||||
if (response.errorMessage != null && "ForbiddenOperationException".equals(response.error)) {
|
||||
if (response.errorMessage.contains("Invalid credentials"))
|
||||
throw new InvalidCredentialsException();
|
||||
else if (response.getErrorMessage().contains("Invalid token"))
|
||||
|
||||
else if (response.errorMessage.contains("Invalid token"))
|
||||
throw new InvalidTokenException();
|
||||
else if (response.getErrorMessage().contains("Invalid username or password"))
|
||||
|
||||
else if (response.errorMessage.contains("Invalid username or password"))
|
||||
throw new InvalidPasswordException();
|
||||
throw new RemoteAuthenticationException(response.getError(), response.getErrorMessage(), response.getCause());
|
||||
}
|
||||
|
||||
throw new RemoteAuthenticationException(response.error, response.errorMessage, response.cause);
|
||||
}
|
||||
}
|
||||
|
||||
private String request(URL url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url, proxy);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
throw new ServerDisconnectException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,77 +179,27 @@ public class YggdrasilService {
|
||||
}
|
||||
}
|
||||
|
||||
static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(GameProfile.class, GameProfile.Serializer.INSTANCE)
|
||||
private class TextureResponse {
|
||||
public Map<TextureType, Texture> textures;
|
||||
}
|
||||
|
||||
private class AuthenticationResponse extends ErrorResponse {
|
||||
public String accessToken;
|
||||
public String clientToken;
|
||||
public GameProfile selectedProfile;
|
||||
public GameProfile[] availableProfiles;
|
||||
public User user;
|
||||
}
|
||||
|
||||
private class ErrorResponse {
|
||||
public String error;
|
||||
public String errorMessage;
|
||||
public String cause;
|
||||
}
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(PropertyMap.class, PropertyMap.Serializer.INSTANCE)
|
||||
.registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE)
|
||||
.create();
|
||||
|
||||
private static final class RefreshRequest {
|
||||
|
||||
private final String accessToken;
|
||||
private final String clientToken;
|
||||
private final GameProfile selectedProfile;
|
||||
private final boolean requestUser;
|
||||
|
||||
public RefreshRequest(String accessToken, String clientToken) {
|
||||
this(accessToken, clientToken, null);
|
||||
}
|
||||
|
||||
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile) {
|
||||
this(accessToken, clientToken, selectedProfile, true);
|
||||
}
|
||||
|
||||
public RefreshRequest(String accessToken, String clientToken, GameProfile selectedProfile, boolean requestUser) {
|
||||
this.accessToken = accessToken;
|
||||
this.clientToken = clientToken;
|
||||
this.selectedProfile = selectedProfile;
|
||||
this.requestUser = requestUser;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return clientToken;
|
||||
}
|
||||
|
||||
public GameProfile getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public boolean isRequestUser() {
|
||||
return requestUser;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ProfileResponse {
|
||||
private final UUID id;
|
||||
private final String name;
|
||||
private final PropertyMap properties;
|
||||
|
||||
public ProfileResponse() {
|
||||
this(UUID.randomUUID(), "", null);
|
||||
}
|
||||
|
||||
public ProfileResponse(UUID id, String name, PropertyMap properties) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public UUID getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public PropertyMap getProperties() {
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,52 @@
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public class YggdrasilSession {
|
||||
|
||||
private final String accessToken;
|
||||
private String clientToken;
|
||||
private String accessToken;
|
||||
private GameProfile selectedProfile;
|
||||
private final GameProfile[] availableProfiles;
|
||||
private final User user;
|
||||
private GameProfile[] availableProfiles;
|
||||
private User user;
|
||||
|
||||
public YggdrasilSession(String accessToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user) {
|
||||
public YggdrasilSession(String clientToken, String accessToken, GameProfile selectedProfile, GameProfile[] availableProfiles, User user) {
|
||||
this.clientToken = clientToken;
|
||||
this.accessToken = accessToken;
|
||||
this.selectedProfile = selectedProfile;
|
||||
this.availableProfiles = availableProfiles;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return clientToken;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return nullable (null if no character is selected)
|
||||
*/
|
||||
public GameProfile getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return nullable (null if the YggdrasilSession is loaded from storage)
|
||||
*/
|
||||
public GameProfile[] getAvailableProfiles() {
|
||||
return availableProfiles;
|
||||
}
|
||||
@@ -37,53 +55,40 @@ public class YggdrasilSession {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setAvailableProfile(GameProfile profile) {
|
||||
if (availableProfiles != null)
|
||||
for (int i = 0; i < availableProfiles.length; ++i)
|
||||
if (availableProfiles[i].getId().equals(profile.getId()))
|
||||
availableProfiles[i] = profile;
|
||||
|
||||
if (selectedProfile != null && profile.getId().equals(selectedProfile.getId()))
|
||||
selectedProfile = profile;
|
||||
}
|
||||
|
||||
public void setSelectedProfile(GameProfile selectedProfile) {
|
||||
this.selectedProfile = selectedProfile;
|
||||
|
||||
setAvailableProfile(selectedProfile);
|
||||
}
|
||||
|
||||
public static YggdrasilSession fromStorage(Map<?, ?> storage) {
|
||||
Optional<String> profileId = Lang.get(storage, "uuid", String.class);
|
||||
Optional<String> profileName = Lang.get(storage, "displayName", String.class);
|
||||
GameProfile profile = null;
|
||||
if (profileId.isPresent() && profileName.isPresent()) {
|
||||
profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get(),
|
||||
Lang.get(storage, "profileProperties", Map.class).map(PropertyMap::fromMap).orElseGet(PropertyMap::new));
|
||||
}
|
||||
|
||||
return new YggdrasilSession(
|
||||
Lang.get(storage, "accessToken", String.class).orElse(null),
|
||||
profile,
|
||||
null,
|
||||
Lang.get(storage, "userid", String.class)
|
||||
.map(userId -> new User(userId, Lang.get(storage, "userProperties", Map.class).map(PropertyMap::fromMap).orElse(null)))
|
||||
.orElse(null)
|
||||
);
|
||||
UUID uuid = tryCast(storage.get("uuid"), String.class).map(UUIDTypeAdapter::fromString).orElseThrow(() -> new IllegalArgumentException("uuid is missing"));
|
||||
String name = tryCast(storage.get("displayName"), String.class).orElseThrow(() -> new IllegalArgumentException("displayName is missing"));
|
||||
String clientToken = tryCast(storage.get("clientToken"), String.class).orElseThrow(() -> new IllegalArgumentException("clientToken is missing"));
|
||||
String accessToken = tryCast(storage.get("accessToken"), String.class).orElseThrow(() -> new IllegalArgumentException("accessToken is missing"));
|
||||
String userId = tryCast(storage.get("userid"), String.class).orElseThrow(() -> new IllegalArgumentException("userid is missing"));
|
||||
PropertyMap userProperties = tryCast(storage.get("userProperties"), Map.class).map(PropertyMap::fromMap).orElse(null);
|
||||
return new YggdrasilSession(clientToken, accessToken, new GameProfile(uuid, name), null, new User(userId, userProperties));
|
||||
}
|
||||
|
||||
public Map<Object, Object> toStorage() {
|
||||
Map<Object, Object> storage = new HashMap<>();
|
||||
storage.put("accessToken", accessToken);
|
||||
if (selectedProfile != null) {
|
||||
storage.put("uuid", UUIDTypeAdapter.fromUUID(selectedProfile.getId()));
|
||||
storage.put("displayName", selectedProfile.getName());
|
||||
storage.put("profileProperties", selectedProfile.getProperties());
|
||||
}
|
||||
if (user != null) {
|
||||
storage.put("userid", user.getId());
|
||||
storage.put("userProperties", user.getProperties());
|
||||
}
|
||||
return storage;
|
||||
if (selectedProfile == null)
|
||||
throw new IllegalStateException("No character is selected");
|
||||
if (user == null)
|
||||
throw new IllegalStateException("No user is specified");
|
||||
|
||||
return mapOf(
|
||||
pair("clientToken", clientToken),
|
||||
pair("accessToken", accessToken),
|
||||
pair("uuid", UUIDTypeAdapter.fromUUID(selectedProfile.getId())),
|
||||
pair("displayName", selectedProfile.getName()),
|
||||
pair("userid", user.getId()),
|
||||
pair("userProperties", user.getProperties()));
|
||||
}
|
||||
|
||||
public AuthInfo toAuthInfo() {
|
||||
if (selectedProfile == null)
|
||||
throw new IllegalStateException("No character is selected");
|
||||
if (user == null)
|
||||
throw new IllegalStateException("No user is specified");
|
||||
|
||||
return new AuthInfo(selectedProfile.getName(), selectedProfile.getId(), accessToken,
|
||||
Optional.ofNullable(user.getProperties()).map(GSON_PROPERTIES::toJson).orElse("{}"));
|
||||
}
|
||||
|
||||
private static final Gson GSON_PROPERTIES = new GsonBuilder().registerTypeAdapter(PropertyMap.class, PropertyMap.LegacySerializer.INSTANCE).create();
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.download;
|
||||
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
|
||||
import org.jackhuang.hmcl.download.game.GameVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
||||
|
||||
/**
|
||||
@@ -34,26 +33,11 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
private BMCLAPIDownloadProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLibraryBaseURL() {
|
||||
return "https://bmclapi2.bangbang93.com/libraries/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionListURL() {
|
||||
return "https://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionBaseURL() {
|
||||
return "https://bmclapi2.bangbang93.com/versions/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetIndexBaseURL() {
|
||||
return "https://bmclapi2.bangbang93.com/indexes/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetBaseURL() {
|
||||
return "https://bmclapi2.bangbang93.com/assets/";
|
||||
|
||||
@@ -24,14 +24,8 @@ package org.jackhuang.hmcl.download;
|
||||
*/
|
||||
public interface DownloadProvider {
|
||||
|
||||
String getLibraryBaseURL();
|
||||
|
||||
String getVersionListURL();
|
||||
|
||||
String getVersionBaseURL();
|
||||
|
||||
String getAssetIndexBaseURL();
|
||||
|
||||
String getAssetBaseURL();
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,26 +28,11 @@ import org.jackhuang.hmcl.download.optifine.OptiFineVersionList;
|
||||
*/
|
||||
public class MojangDownloadProvider implements DownloadProvider {
|
||||
|
||||
@Override
|
||||
public String getLibraryBaseURL() {
|
||||
return "https://libraries.minecraft.net/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionListURL() {
|
||||
return "https://launchermeta.mojang.com/mc/game/version_manifest.json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionBaseURL() {
|
||||
return "https://s3.amazonaws.com/Minecraft.Download/versions/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetIndexBaseURL() {
|
||||
return "https://s3.amazonaws.com/Minecraft.Download/indexes/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetBaseURL() {
|
||||
return "https://resources.download.minecraft.net/";
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.game.AssetObject;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
@@ -35,6 +34,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -91,7 +93,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
try {
|
||||
// check the checksum of file to ensure that the file is not need to re-download.
|
||||
if (file.exists()) {
|
||||
String sha1 = DigestUtils.sha1Hex(FileUtils.readBytes(file));
|
||||
String sha1 = encodeHex(digest("SHA-1", FileUtils.readBytes(file)));
|
||||
if (sha1.equals(assetObject.getHash())) {
|
||||
++downloaded;
|
||||
Logging.LOG.finest("File $file has been downloaded successfully, skipped downloading");
|
||||
|
||||
@@ -33,6 +33,8 @@ import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
* This task is to extract all asset objects described in asset index json.
|
||||
*
|
||||
@@ -82,7 +84,7 @@ public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File,
|
||||
if (Thread.interrupted())
|
||||
throw new InterruptedException();
|
||||
|
||||
res.add(new Pair<>(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject));
|
||||
res.add(pair(dependencyManager.getGameRepository().getAssetObject(version.getId(), assetIndexInfo.getId(), assetObject), assetObject));
|
||||
updateProgress(++progress, index.getObjects().size());
|
||||
}
|
||||
setResult(res);
|
||||
|
||||
@@ -22,11 +22,13 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Platform;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
|
||||
@@ -5,7 +5,10 @@ import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
@@ -15,6 +18,9 @@ import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
|
||||
public final class LibraryDownloadTask extends Task {
|
||||
private final FileDownloadTask xzTask;
|
||||
private final FileDownloadTask task;
|
||||
@@ -77,7 +83,7 @@ public final class LibraryDownloadTask extends Task {
|
||||
return true;
|
||||
}
|
||||
byte[] fileData = FileUtils.readBytes(libPath);
|
||||
boolean valid = checksums.contains(DigestUtils.sha1Hex(fileData));
|
||||
boolean valid = checksums.contains(encodeHex(digest("SHA-1", fileData)));
|
||||
if ((!valid) && (libPath.getName().endsWith(".jar"))) {
|
||||
}
|
||||
return validateJar(fileData, checksums);
|
||||
@@ -98,7 +104,7 @@ public final class LibraryDownloadTask extends Task {
|
||||
hashes = new String(eData, Charset.forName("UTF-8")).split("\n");
|
||||
}
|
||||
if (!entry.isDirectory()) {
|
||||
files.put(entry.getName(), DigestUtils.sha1Hex(eData));
|
||||
files.put(entry.getName(), encodeHex(digest("SHA-1", eData)));
|
||||
}
|
||||
entry = jar.getNextJarEntry();
|
||||
}
|
||||
|
||||
@@ -54,28 +54,22 @@ public final class Arguments {
|
||||
return jvm == null ? Collections.emptyList() : Collections.unmodifiableList(jvm);
|
||||
}
|
||||
|
||||
public static Arguments addGameArguments(Arguments arguments, String... gameArguments) {
|
||||
return addGameArguments(arguments, Arrays.asList(gameArguments));
|
||||
public Arguments addGameArguments(String... gameArguments) {
|
||||
return addGameArguments(Arrays.asList(gameArguments));
|
||||
}
|
||||
|
||||
public static Arguments addGameArguments(Arguments arguments, List<String> gameArguments) {
|
||||
public Arguments addGameArguments(List<String> gameArguments) {
|
||||
List<Argument> list = gameArguments.stream().map(StringArgument::new).collect(Collectors.toList());
|
||||
if (arguments == null)
|
||||
return new Arguments(list, null);
|
||||
else
|
||||
return new Arguments(Lang.merge(arguments.getGame(), list), arguments.getJvm());
|
||||
return new Arguments(Lang.merge(getGame(), list), getJvm());
|
||||
}
|
||||
|
||||
public static Arguments addJVMArguments(Arguments arguments, String... jvmArguments) {
|
||||
return addJVMArguments(arguments, Arrays.asList(jvmArguments));
|
||||
public Arguments addJVMArguments(String... jvmArguments) {
|
||||
return addJVMArguments(Arrays.asList(jvmArguments));
|
||||
}
|
||||
|
||||
public static Arguments addJVMArguments(Arguments arguments, List<String> jvmArguments) {
|
||||
public Arguments addJVMArguments(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));
|
||||
return new Arguments(getGame(), Lang.merge(getJvm(), list));
|
||||
}
|
||||
|
||||
public static Arguments merge(Arguments a, Arguments b) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* An implementation of classic Minecraft game repository.
|
||||
@@ -154,8 +155,26 @@ public class DefaultGameRepository implements GameRepository {
|
||||
File file = getVersionRoot(id);
|
||||
if (!file.exists())
|
||||
return true;
|
||||
// test if no file in this version directory is occupied.
|
||||
File removedFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + "_removed");
|
||||
if (!file.renameTo(removedFile))
|
||||
return false;
|
||||
|
||||
// remove json files first to ensure HMCL will not recognize this folder as a valid version.
|
||||
List<File> jsons = FileUtils.listFilesByExtension(removedFile, "json");
|
||||
jsons.forEach(f -> {
|
||||
if (!f.delete())
|
||||
Logging.LOG.warning("Unable to delete file " + f);
|
||||
});
|
||||
|
||||
versions.remove(id);
|
||||
return FileUtils.deleteDirectoryQuietly(file);
|
||||
// remove the version from version list regardless of whether the directory was removed successfully or not.
|
||||
try {
|
||||
FileUtils.deleteDirectory(removedFile);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to remove version folder: " + file, e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void refreshVersionsImpl() {
|
||||
@@ -170,51 +189,50 @@ public class DefaultGameRepository implements GameRepository {
|
||||
|
||||
File[] files = new File(getBaseDirectory(), "versions").listFiles();
|
||||
if (files != null)
|
||||
for (File dir : files)
|
||||
if (dir.isDirectory()) {
|
||||
String id = dir.getName();
|
||||
File json = new File(dir, id + ".json");
|
||||
Arrays.stream(files).parallel().filter(File::isDirectory).flatMap(dir -> {
|
||||
String id = dir.getName();
|
||||
File json = new File(dir, id + ".json");
|
||||
|
||||
// If user renamed the json file by mistake or created the json file in a wrong name,
|
||||
// we will find the only json and rename it to correct name.
|
||||
if (!json.exists()) {
|
||||
List<File> jsons = FileUtils.listFilesByExtension(dir, "json");
|
||||
if (jsons.size() == 1)
|
||||
if (!jsons.get(0).renameTo(json)) {
|
||||
Logging.LOG.warning("Cannot rename json file " + jsons.get(0) + " to " + json + ", ignoring version " + id);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// If user renamed the json file by mistake or created the json file in a wrong name,
|
||||
// we will find the only json and rename it to correct name.
|
||||
if (!json.exists()) {
|
||||
List<File> jsons = FileUtils.listFilesByExtension(dir, "json");
|
||||
if (jsons.size() == 1)
|
||||
if (!jsons.get(0).renameTo(json)) {
|
||||
Logging.LOG.warning("Cannot rename json file " + jsons.get(0) + " to " + json + ", ignoring version " + id);
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
Version version;
|
||||
try {
|
||||
version = Objects.requireNonNull(readVersionJson(json));
|
||||
} catch (Exception e) {
|
||||
// JsonSyntaxException or IOException or NullPointerException(!!)
|
||||
if (EventBus.EVENT_BUS.fireEvent(new GameJsonParseFailedEvent(this, json, id)) != Event.Result.ALLOW)
|
||||
return Stream.empty();
|
||||
|
||||
Version version;
|
||||
try {
|
||||
version = Objects.requireNonNull(readVersionJson(json));
|
||||
} catch (Exception e) {
|
||||
// JsonSyntaxException or IOException or NullPointerException(!!)
|
||||
if (EventBus.EVENT_BUS.fireEvent(new GameJsonParseFailedEvent(this, json, id)) != Event.Result.ALLOW)
|
||||
continue;
|
||||
|
||||
try {
|
||||
version = Objects.requireNonNull(readVersionJson(json));
|
||||
} catch (Exception e2) {
|
||||
Logging.LOG.log(Level.SEVERE, "User corrected version json is still malformed");
|
||||
continue;
|
||||
}
|
||||
} catch (Exception e2) {
|
||||
Logging.LOG.log(Level.SEVERE, "User corrected version json is still malformed");
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
if (!id.equals(version.getId())) {
|
||||
version = version.setId(id);
|
||||
try {
|
||||
FileUtils.writeText(json, Constants.GSON.toJson(version));
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Ignoring version " + id + " because wrong id " + version.getId() + " is set and cannot correct it.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
provider.addVersion(version);
|
||||
}
|
||||
|
||||
if (!id.equals(version.getId())) {
|
||||
version = version.setId(id);
|
||||
try {
|
||||
FileUtils.writeText(json, Constants.GSON.toJson(version));
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Ignoring version " + id + " because wrong id " + version.getId() + " is set and cannot correct it.");
|
||||
return Stream.empty();
|
||||
}
|
||||
}
|
||||
|
||||
return Stream.of(version);
|
||||
}).forEachOrdered(provider::addVersion);
|
||||
|
||||
for (Version version : provider.getVersionMap().values()) {
|
||||
try {
|
||||
Version resolved = version.resolve(provider);
|
||||
|
||||
@@ -19,13 +19,14 @@ package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile;
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -55,7 +56,7 @@ public final class GameVersion {
|
||||
private static Optional<String> getVersionOfOldMinecraft(ZipFile file, ZipArchiveEntry entry) throws IOException {
|
||||
byte[] tmp = IOUtils.readFullyAsByteArray(file.getInputStream(entry));
|
||||
|
||||
byte[] bytes = "Minecraft Minecraft ".getBytes(Charsets.US_ASCII);
|
||||
byte[] bytes = "Minecraft Minecraft ".getBytes(US_ASCII);
|
||||
int j = matchArray(tmp, bytes);
|
||||
if (j < 0)
|
||||
return Optional.empty();
|
||||
@@ -64,25 +65,25 @@ public final class GameVersion {
|
||||
if ((j = lessThan32(tmp, i)) < 0)
|
||||
return Optional.empty();
|
||||
|
||||
return Optional.of(new String(tmp, i, j - i, Charsets.US_ASCII));
|
||||
return Optional.of(new String(tmp, i, j - i, US_ASCII));
|
||||
}
|
||||
|
||||
private static Optional<String> getVersionOfNewMinecraft(ZipFile file, ZipArchiveEntry entry) throws IOException {
|
||||
byte[] tmp = IOUtils.readFullyAsByteArray(file.getInputStream(entry));
|
||||
|
||||
byte[] str = "-server.txt".getBytes(Charsets.US_ASCII);
|
||||
byte[] str = "-server.txt".getBytes(US_ASCII);
|
||||
int j = matchArray(tmp, str);
|
||||
if (j < 0) return Optional.empty();
|
||||
int i = j + str.length;
|
||||
i += 11;
|
||||
j = lessThan32(tmp, i);
|
||||
if (j < 0) return Optional.empty();
|
||||
String result = new String(tmp, i, j - i, Charsets.US_ASCII);
|
||||
String result = new String(tmp, i, j - i, US_ASCII);
|
||||
|
||||
char ch = result.charAt(0);
|
||||
// 1.8.1+
|
||||
if (ch < '0' || ch > '9') {
|
||||
str = "Can't keep up! Did the system time change, or is the server overloaded?".getBytes(Charsets.US_ASCII);
|
||||
str = "Can't keep up! Did the system time change, or is the server overloaded?".getBytes(US_ASCII);
|
||||
j = matchArray(tmp, str);
|
||||
if (j < 0) return Optional.empty();
|
||||
i = -1;
|
||||
@@ -100,7 +101,7 @@ public final class GameVersion {
|
||||
while (tmp[k] >= 48 && tmp[k] <= 57 || tmp[k] == (int) '-' || tmp[k] == (int) '.' || tmp[k] >= 97 && tmp[k] <= (int) 'z')
|
||||
k--;
|
||||
k++;
|
||||
return Optional.of(new String(tmp, k, i - k + 1, Charsets.US_ASCII));
|
||||
return Optional.of(new String(tmp, k, i - k + 1, US_ASCII));
|
||||
}
|
||||
return Optional.of(result);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,9 @@ import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -191,8 +194,8 @@ public class DefaultLauncher extends Launcher {
|
||||
);
|
||||
}
|
||||
|
||||
private final Map<String, Supplier<Boolean>> forbiddens = Lang.mapOf(
|
||||
new Pair<String, Supplier<Boolean>>("-Xincgc", () -> options.getJava().getParsedVersion() >= JavaVersion.JAVA_9)
|
||||
private final Map<String, Supplier<Boolean>> forbiddens = mapOf(
|
||||
pair("-Xincgc", () -> options.getJava().getParsedVersion() >= JavaVersion.JAVA_9)
|
||||
);
|
||||
|
||||
protected Map<String, Supplier<Boolean>> getForbiddens() {
|
||||
@@ -232,18 +235,18 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
|
||||
protected Map<String, String> getConfigurations() {
|
||||
return Lang.mapOf(
|
||||
new Pair<>("${auth_player_name}", authInfo.getUsername()),
|
||||
new Pair<>("${auth_session}", authInfo.getAccessToken()),
|
||||
new Pair<>("${auth_access_token}", authInfo.getAccessToken()),
|
||||
new Pair<>("${auth_uuid}", authInfo.getUserId()),
|
||||
new Pair<>("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
|
||||
new Pair<>("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
|
||||
new Pair<>("${version_type}", version.getType().getId()),
|
||||
new Pair<>("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()),
|
||||
new Pair<>("${user_type}", authInfo.getUserType().toString().toLowerCase()),
|
||||
new Pair<>("${assets_index_name}", version.getAssetIndex().getId()),
|
||||
new Pair<>("${user_properties}", authInfo.getUserProperties())
|
||||
return mapOf(
|
||||
pair("${auth_player_name}", authInfo.getUsername()),
|
||||
pair("${auth_session}", authInfo.getAccessToken()),
|
||||
pair("${auth_access_token}", authInfo.getAccessToken()),
|
||||
pair("${auth_uuid}", UUIDTypeAdapter.fromUUID(authInfo.getUUID())),
|
||||
pair("${version_name}", Optional.ofNullable(options.getVersionName()).orElse(version.getId())),
|
||||
pair("${profile_name}", Optional.ofNullable(options.getProfileName()).orElse("Minecraft")),
|
||||
pair("${version_type}", version.getType().getId()),
|
||||
pair("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()),
|
||||
pair("${user_type}", "mojang"),
|
||||
pair("${assets_index_name}", version.getAssetIndex().getId()),
|
||||
pair("${user_properties}", authInfo.getUserProperties())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -270,9 +273,7 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
|
||||
ManagedProcess p = new ManagedProcess(process, rawCommandLine);
|
||||
if (listener == null)
|
||||
startMonitors(p);
|
||||
else
|
||||
if (listener != null)
|
||||
startMonitors(p, listener, daemon);
|
||||
return p;
|
||||
}
|
||||
@@ -310,11 +311,6 @@ public class DefaultLauncher extends Launcher {
|
||||
throw new PermissionException();
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess) {
|
||||
managedProcess.addRelatedThread(Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream()), "stdout-pump", true));
|
||||
managedProcess.addRelatedThread(Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream()), "stderr-pump", true));
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener) {
|
||||
startMonitors(managedProcess, processListener, true);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
@@ -29,6 +28,9 @@ import java.io.FileInputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
|
||||
public final class MinecraftInstanceTask<T> extends Task {
|
||||
|
||||
private final File zipFile;
|
||||
@@ -62,7 +64,7 @@ public final class MinecraftInstanceTask<T> extends Task {
|
||||
if (path.startsWith("/") || path.startsWith("\\"))
|
||||
path = path.substring(1);
|
||||
|
||||
overrides.add(new ModpackConfiguration.FileInformation(path, DigestUtils.sha1Hex(zip)));
|
||||
overrides.add(new ModpackConfiguration.FileInformation(path, encodeHex(digest("SHA-1", zip))));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.mod;
|
||||
import org.apache.commons.compress.archivers.ArchiveEntry;
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
|
||||
@@ -31,6 +30,9 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
import static org.jackhuang.hmcl.util.Hex.encodeHex;
|
||||
|
||||
public class ModpackInstallTask<T> extends Task {
|
||||
|
||||
private final File modpackFile;
|
||||
@@ -92,8 +94,8 @@ public class ModpackInstallTask<T> extends Task {
|
||||
byte[] data = os.toByteArray();
|
||||
|
||||
if (files.contains(path) && entryFile.exists()) {
|
||||
String oldHash = DigestUtils.sha1Hex(new FileInputStream(entryFile));
|
||||
String newHash = DigestUtils.sha1Hex(new ByteArrayInputStream(data));
|
||||
String oldHash = encodeHex(digest("SHA-1", new FileInputStream(entryFile)));
|
||||
String newHash = encodeHex(digest("SHA-1", new ByteArrayInputStream(data)));
|
||||
if (!oldHash.equals(newHash)) {
|
||||
try (FileOutputStream fos = new FileOutputStream(entryFile)) {
|
||||
IOUtils.copyTo(new ByteArrayInputStream(data), fos, buf);
|
||||
|
||||
@@ -109,7 +109,7 @@ public final class MultiMCModpackInstallTask extends Task {
|
||||
version = version
|
||||
.setLibraries(Lang.merge(version.getLibraries(), patch.getLibraries()))
|
||||
.setMainClass(patch.getMainClass())
|
||||
.setArguments(Arguments.addGameArguments(version.getArguments().orElse(null), newArguments));
|
||||
.setArguments(version.getArguments().orElseGet(Arguments::new).addGameArguments(newArguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,10 @@ package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.event.FailedEvent;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -32,6 +35,8 @@ import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
||||
|
||||
/**
|
||||
* A task that can download a file online.
|
||||
*
|
||||
@@ -154,7 +159,7 @@ public class FileDownloadTask extends Task {
|
||||
temp = FileUtils.createTempFile();
|
||||
rFile = new RandomAccessFile(temp, "rw");
|
||||
|
||||
MessageDigest digest = DigestUtils.getSha1Digest();
|
||||
MessageDigest digest = getDigest("SHA-1");
|
||||
|
||||
stream = con.getInputStream();
|
||||
int lastDownloaded = 0, downloaded = 0;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task;
|
||||
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
@@ -31,6 +30,8 @@ import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
@@ -52,7 +53,7 @@ public final class GetTask extends TaskResult<String> {
|
||||
}
|
||||
|
||||
public GetTask(URL url, Proxy proxy, String id) {
|
||||
this(url, proxy, id, Charsets.DEFAULT_CHARSET);
|
||||
this(url, proxy, id, UTF_8);
|
||||
}
|
||||
|
||||
public GetTask(URL url, Proxy proxy, String id, Charset charset) {
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.Collection;
|
||||
*/
|
||||
public final class ParallelTask extends Task {
|
||||
|
||||
private final Task[] tasks;
|
||||
private final Collection<Task> tasks;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -36,6 +36,11 @@ public final class ParallelTask extends Task {
|
||||
* @param tasks the tasks that can be executed parallelly.
|
||||
*/
|
||||
public ParallelTask(Task... tasks) {
|
||||
this.tasks = Arrays.asList(tasks);
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
|
||||
public ParallelTask(Collection<Task> tasks) {
|
||||
this.tasks = tasks;
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
@@ -46,7 +51,7 @@ public final class ParallelTask extends Task {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Arrays.asList(tasks);
|
||||
return tasks;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,12 +119,12 @@ public final class Schedulers {
|
||||
Logging.LOG.info("Shutting down executor services.");
|
||||
|
||||
if (CACHED_EXECUTOR != null)
|
||||
CACHED_EXECUTOR.shutdown();
|
||||
CACHED_EXECUTOR.shutdownNow();
|
||||
|
||||
if (IO_EXECUTOR != null)
|
||||
IO_EXECUTOR.shutdown();
|
||||
IO_EXECUTOR.shutdownNow();
|
||||
|
||||
if (SINGLE_EXECUTOR != null)
|
||||
SINGLE_EXECUTOR.shutdown();
|
||||
SINGLE_EXECUTOR.shutdownNow();
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft!.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
|
||||
public final class Charsets {
|
||||
|
||||
private Charsets() {
|
||||
}
|
||||
|
||||
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||
|
||||
public static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||
|
||||
public static final Charset UTF_16 = Charset.forName("UTF-16");
|
||||
|
||||
public static final Charset UTF_16BE = Charset.forName("UTF-16BE");
|
||||
|
||||
public static final Charset UTF_16LE = Charset.forName("UTF-16LE");
|
||||
|
||||
public static final Charset UTF_8 = Charset.forName("UTF-8");
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = UTF_8;
|
||||
|
||||
public static Charset toCharset(String charset) {
|
||||
if (charset == null)
|
||||
return Charset.defaultCharset();
|
||||
try {
|
||||
return Charset.forName(charset);
|
||||
} catch (UnsupportedCharsetException ignored) {
|
||||
return Charset.defaultCharset();
|
||||
}
|
||||
}
|
||||
|
||||
public static Charset toCharset() {
|
||||
return toCharset(System.getProperty("sun.jnu.encoding", Constants.DEFAULT_ENCODING));
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,6 @@ public final class Constants {
|
||||
private Constants() {
|
||||
}
|
||||
|
||||
public static final String DEFAULT_ENCODING = "UTF-8";
|
||||
public static final Charset DEFAULT_CHARSET = Charsets.UTF_8;
|
||||
public static final Charset SYSTEM_CHARSET = Charset.forName(OperatingSystem.ENCODING);
|
||||
|
||||
public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/";
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -33,11 +35,6 @@ public final class DigestUtils {
|
||||
|
||||
private static final int STREAM_BUFFER_LENGTH = 1024;
|
||||
|
||||
private static byte[] digest(MessageDigest digest, InputStream data)
|
||||
throws IOException {
|
||||
return updateDigest(digest, data).digest();
|
||||
}
|
||||
|
||||
public static MessageDigest getDigest(String algorithm) {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm);
|
||||
@@ -46,193 +43,23 @@ public final class DigestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static MessageDigest getMd2Digest() {
|
||||
return getDigest("MD2");
|
||||
public static byte[] digest(String algorithm, String data) {
|
||||
return digest(algorithm, data.getBytes(UTF_8));
|
||||
}
|
||||
|
||||
public static MessageDigest getMd5Digest() {
|
||||
return getDigest("MD5");
|
||||
public static byte[] digest(String algorithm, byte[] data) {
|
||||
return getDigest(algorithm).digest(data);
|
||||
}
|
||||
|
||||
public static MessageDigest getSha1Digest() {
|
||||
return getDigest("SHA-1");
|
||||
public static byte[] digest(String algorithm, InputStream data) throws IOException {
|
||||
return digest(getDigest(algorithm), data);
|
||||
}
|
||||
|
||||
public static MessageDigest getSha256Digest() {
|
||||
return getDigest("SHA-256");
|
||||
public static byte[] digest(MessageDigest digest, InputStream data) throws IOException {
|
||||
return updateDigest(digest, data).digest();
|
||||
}
|
||||
|
||||
public static MessageDigest getSha384Digest() {
|
||||
return getDigest("SHA-384");
|
||||
}
|
||||
|
||||
public static MessageDigest getSha512Digest() {
|
||||
return getDigest("SHA-512");
|
||||
}
|
||||
|
||||
public static byte[] md2(byte[] data) {
|
||||
return getMd2Digest().digest(data);
|
||||
}
|
||||
|
||||
public static byte[] md2(InputStream data)
|
||||
throws IOException {
|
||||
return digest(getMd2Digest(), data);
|
||||
}
|
||||
|
||||
public static byte[] md2(String data) {
|
||||
return md2(data.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String md2Hex(byte[] data) {
|
||||
return Hex.encodeHexString(md2(data));
|
||||
}
|
||||
|
||||
public static String md2Hex(InputStream data)
|
||||
throws IOException {
|
||||
return Hex.encodeHexString(md2(data));
|
||||
}
|
||||
|
||||
public static String md2Hex(String data) {
|
||||
return Hex.encodeHexString(md2(data));
|
||||
}
|
||||
|
||||
public static byte[] md5(byte[] data) {
|
||||
return getMd5Digest().digest(data);
|
||||
}
|
||||
|
||||
public static byte[] md5(InputStream data)
|
||||
throws IOException {
|
||||
return digest(getMd5Digest(), data);
|
||||
}
|
||||
|
||||
public static byte[] md5(String data) {
|
||||
return md5(data.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String md5Hex(byte[] data) {
|
||||
return Hex.encodeHexString(md5(data));
|
||||
}
|
||||
|
||||
public static String md5Hex(InputStream data)
|
||||
throws IOException {
|
||||
return Hex.encodeHexString(md5(data));
|
||||
}
|
||||
|
||||
public static String md5Hex(String data) {
|
||||
return Hex.encodeHexString(md5(data));
|
||||
}
|
||||
|
||||
public static byte[] sha1(byte[] data) {
|
||||
return getSha1Digest().digest(data);
|
||||
}
|
||||
|
||||
public static byte[] sha1(InputStream data)
|
||||
throws IOException {
|
||||
return digest(getSha1Digest(), data);
|
||||
}
|
||||
|
||||
public static byte[] sha1(String data) {
|
||||
return sha1(data.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String sha1Hex(byte[] data) {
|
||||
return Hex.encodeHexString(sha1(data));
|
||||
}
|
||||
|
||||
public static String sha1Hex(InputStream data)
|
||||
throws IOException {
|
||||
return Hex.encodeHexString(sha1(data));
|
||||
}
|
||||
|
||||
public static String sha1Hex(String data) {
|
||||
return Hex.encodeHexString(sha1(data));
|
||||
}
|
||||
|
||||
public static byte[] sha256(byte[] data) {
|
||||
return getSha256Digest().digest(data);
|
||||
}
|
||||
|
||||
public static byte[] sha256(InputStream data)
|
||||
throws IOException {
|
||||
return digest(getSha256Digest(), data);
|
||||
}
|
||||
|
||||
public static byte[] sha256(String data) {
|
||||
return sha256(data.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String sha256Hex(byte[] data) {
|
||||
return Hex.encodeHexString(sha256(data));
|
||||
}
|
||||
|
||||
public static String sha256Hex(InputStream data)
|
||||
throws IOException {
|
||||
return Hex.encodeHexString(sha256(data));
|
||||
}
|
||||
|
||||
public static String sha256Hex(String data) {
|
||||
return Hex.encodeHexString(sha256(data));
|
||||
}
|
||||
|
||||
public static byte[] sha384(byte[] data) {
|
||||
return getSha384Digest().digest(data);
|
||||
}
|
||||
|
||||
public static byte[] sha384(InputStream data)
|
||||
throws IOException {
|
||||
return digest(getSha384Digest(), data);
|
||||
}
|
||||
|
||||
public static byte[] sha384(String data) {
|
||||
return sha384(data.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String sha384Hex(byte[] data) {
|
||||
return Hex.encodeHexString(sha384(data));
|
||||
}
|
||||
|
||||
public static String sha384Hex(InputStream data)
|
||||
throws IOException {
|
||||
return Hex.encodeHexString(sha384(data));
|
||||
}
|
||||
|
||||
public static String sha384Hex(String data) {
|
||||
return Hex.encodeHexString(sha384(data));
|
||||
}
|
||||
|
||||
public static byte[] sha512(byte[] data) {
|
||||
return getSha512Digest().digest(data);
|
||||
}
|
||||
|
||||
public static byte[] sha512(InputStream data)
|
||||
throws IOException {
|
||||
return digest(getSha512Digest(), data);
|
||||
}
|
||||
|
||||
public static byte[] sha512(String data) {
|
||||
return sha512(data.getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String sha512Hex(byte[] data) {
|
||||
return Hex.encodeHexString(sha512(data));
|
||||
}
|
||||
|
||||
public static String sha512Hex(InputStream data)
|
||||
throws IOException {
|
||||
return Hex.encodeHexString(sha512(data));
|
||||
}
|
||||
|
||||
public static String sha512Hex(String data) {
|
||||
return Hex.encodeHexString(sha512(data));
|
||||
}
|
||||
|
||||
public static MessageDigest updateDigest(MessageDigest messageDigest, byte[] valueToDigest) {
|
||||
messageDigest.update(valueToDigest);
|
||||
return messageDigest;
|
||||
}
|
||||
|
||||
public static MessageDigest updateDigest(MessageDigest digest, InputStream data)
|
||||
throws IOException {
|
||||
public static MessageDigest updateDigest(MessageDigest digest, InputStream data) throws IOException {
|
||||
byte[] buffer = new byte[STREAM_BUFFER_LENGTH];
|
||||
int read = data.read(buffer, 0, STREAM_BUFFER_LENGTH);
|
||||
|
||||
@@ -244,8 +71,4 @@ public final class DigestUtils {
|
||||
return digest;
|
||||
}
|
||||
|
||||
public static MessageDigest updateDigest(MessageDigest messageDigest, String valueToDigest) {
|
||||
messageDigest.update(valueToDigest.getBytes(Charsets.UTF_8));
|
||||
return messageDigest;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
@@ -44,7 +46,7 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
public static String readText(File file) throws IOException {
|
||||
return readText(file, Charsets.DEFAULT_CHARSET);
|
||||
return readText(file, UTF_8);
|
||||
}
|
||||
|
||||
public static String readText(File file, Charset charset) throws IOException {
|
||||
@@ -60,7 +62,7 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
public static void writeText(File file, String text) throws IOException {
|
||||
writeText(file, text, Charsets.DEFAULT_CHARSET);
|
||||
writeText(file, text, UTF_8);
|
||||
}
|
||||
|
||||
public static void writeText(File file, String text, Charset charset) throws IOException {
|
||||
|
||||
@@ -17,20 +17,18 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.io.IOException;
|
||||
|
||||
public final class Hex {
|
||||
|
||||
private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
private final Charset charset;
|
||||
|
||||
public static byte[] decodeHex(char[] data) throws Exception {
|
||||
public static byte[] decodeHex(String str) throws IOException {
|
||||
char[] data = str.toCharArray();
|
||||
int len = data.length;
|
||||
|
||||
if ((len & 0x1) != 0)
|
||||
throw new Exception("Odd number of characters.");
|
||||
throw new IOException("Odd number of characters.");
|
||||
|
||||
byte[] out = new byte[len >> 1];
|
||||
|
||||
@@ -46,83 +44,24 @@ public final class Hex {
|
||||
return out;
|
||||
}
|
||||
|
||||
public static char[] encodeHex(byte[] data) {
|
||||
return encodeHex(data, true);
|
||||
}
|
||||
|
||||
public static char[] encodeHex(byte[] data, boolean toLowerCase) {
|
||||
return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER);
|
||||
}
|
||||
|
||||
protected static char[] encodeHex(byte[] data, char[] toDigits) {
|
||||
public static String encodeHex(byte[] data) {
|
||||
int l = data.length;
|
||||
char[] out = new char[l << 1];
|
||||
|
||||
int i = 0;
|
||||
for (int j = 0; i < l; i++) {
|
||||
out[(j++)] = toDigits[((0xF0 & data[i]) >>> 4)];
|
||||
out[(j++)] = toDigits[(0xF & data[i])];
|
||||
out[(j++)] = DIGITS_LOWER[((0xF0 & data[i]) >>> 4)];
|
||||
out[(j++)] = DIGITS_LOWER[(0xF & data[i])];
|
||||
}
|
||||
return out;
|
||||
return new String(out);
|
||||
}
|
||||
|
||||
public static String encodeHexString(byte[] data) {
|
||||
return new String(encodeHex(data));
|
||||
}
|
||||
|
||||
protected static int toDigit(char ch, int index) {
|
||||
private static int toDigit(char ch, int index) throws IOException {
|
||||
int digit = Character.digit(ch, 16);
|
||||
if (digit == -1)
|
||||
throw new IllegalArgumentException("Illegal hexadecimal character " + ch + " at index " + index);
|
||||
throw new IOException("Illegal hexadecimal character " + ch + " at index " + index);
|
||||
return digit;
|
||||
}
|
||||
|
||||
public Hex() {
|
||||
this(Charsets.DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
public Hex(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public byte[] decode(byte[] array) throws Exception {
|
||||
return decodeHex(new String(array, getCharset()).toCharArray());
|
||||
}
|
||||
|
||||
public Object decode(Object object) throws Exception {
|
||||
try {
|
||||
char[] charArray = (object instanceof String) ? ((String) object).toCharArray() : (char[]) object;
|
||||
return decodeHex(charArray);
|
||||
} catch (ClassCastException e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] encode(byte[] array) {
|
||||
return encodeHexString(array).getBytes(getCharset());
|
||||
}
|
||||
|
||||
public Object encode(Object object)
|
||||
throws Exception {
|
||||
try {
|
||||
byte[] byteArray = (object instanceof String) ? ((String) object).getBytes(getCharset()) : (byte[]) object;
|
||||
|
||||
return encodeHex(byteArray);
|
||||
} catch (ClassCastException e) {
|
||||
throw new Exception(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
return this.charset;
|
||||
}
|
||||
|
||||
public String getCharsetName() {
|
||||
return this.charset.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "[charsetName=" + this.charset + "]";
|
||||
}
|
||||
private Hex() {}
|
||||
}
|
||||
@@ -18,10 +18,20 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.LinkOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.logging.Level;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Represents a Java installation.
|
||||
@@ -69,7 +79,7 @@ public final class JavaVersion implements Serializable {
|
||||
return version;
|
||||
}
|
||||
|
||||
private static final Pattern REGEX = Pattern.compile("java version \"(?<version>(.*?))\"");
|
||||
private static final Pattern REGEX = Pattern.compile("version \"(?<version>(.*?))\"");
|
||||
|
||||
public static final int UNKNOWN = -1;
|
||||
public static final int JAVA_5 = 50;
|
||||
@@ -155,10 +165,14 @@ public final class JavaVersion implements Serializable {
|
||||
Platform.PLATFORM
|
||||
);
|
||||
|
||||
private static Map<String, JavaVersion> JAVAS;
|
||||
private static List<JavaVersion> JAVAS;
|
||||
private static final CountDownLatch LATCH = new CountDownLatch(1);
|
||||
|
||||
public static Map<String, JavaVersion> getJREs() throws InterruptedException {
|
||||
public static Optional<List<JavaVersion>> getJREsImmediately() {
|
||||
return Optional.ofNullable(JAVAS);
|
||||
}
|
||||
|
||||
public static List<JavaVersion> getJREs() throws InterruptedException {
|
||||
if (JAVAS != null)
|
||||
return JAVAS;
|
||||
LATCH.await();
|
||||
@@ -168,13 +182,14 @@ public final class JavaVersion implements Serializable {
|
||||
public static synchronized void initialize() throws IOException {
|
||||
if (JAVAS != null)
|
||||
throw new IllegalStateException("JavaVersions have already been initialized.");
|
||||
HashMap<String, JavaVersion> temp = new HashMap<>();
|
||||
temp.put(THIS_JAVA.getVersion(), THIS_JAVA);
|
||||
List<JavaVersion> javaVersions;
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case WINDOWS:
|
||||
javaVersions = queryWindows();
|
||||
break;
|
||||
case LINUX:
|
||||
javaVersions = queryLinux();
|
||||
break;
|
||||
case OSX:
|
||||
javaVersions = queryMacintosh();
|
||||
break;
|
||||
@@ -182,12 +197,44 @@ public final class JavaVersion implements Serializable {
|
||||
javaVersions = Collections.emptyList();
|
||||
break;
|
||||
}
|
||||
for (JavaVersion v : javaVersions)
|
||||
temp.put(v.getVersion(), v);
|
||||
JAVAS = Collections.unmodifiableMap(temp);
|
||||
|
||||
boolean isCurrentJavaIncluded = false;
|
||||
for (int i = 0; i < javaVersions.size(); i++) {
|
||||
if (THIS_JAVA.getBinary().equals(javaVersions.get(i).getBinary())) {
|
||||
javaVersions.set(i, THIS_JAVA);
|
||||
isCurrentJavaIncluded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isCurrentJavaIncluded) {
|
||||
javaVersions.add(THIS_JAVA);
|
||||
}
|
||||
|
||||
JAVAS = Collections.unmodifiableList(javaVersions);
|
||||
LATCH.countDown();
|
||||
}
|
||||
|
||||
private static List<JavaVersion> queryLinux() throws IOException {
|
||||
Path jvmDir = Paths.get("/usr/lib/jvm");
|
||||
if (Files.isDirectory(jvmDir)) {
|
||||
return Files.list(jvmDir)
|
||||
.filter(dir -> Files.isDirectory(dir, LinkOption.NOFOLLOW_LINKS))
|
||||
.map(dir -> dir.resolve("bin/java"))
|
||||
.filter(Files::isExecutable)
|
||||
.flatMap(executable -> {
|
||||
try {
|
||||
return Stream.of(fromExecutable(executable.toFile()));
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Couldn't determine java " + executable, e);
|
||||
return Stream.empty();
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<JavaVersion> queryMacintosh() throws IOException {
|
||||
LinkedList<JavaVersion> res = new LinkedList<>();
|
||||
|
||||
|
||||
@@ -218,12 +218,12 @@ public final class Lang {
|
||||
* @param <V> the type that {@code obj} is being cast to.
|
||||
* @return {@code obj} in the type of {@code V}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <V> Optional<V> cast(Object obj, Class<V> clazz) {
|
||||
if (obj == null || !ReflectionHelper.isInstance(clazz, obj))
|
||||
public static <V> Optional<V> tryCast(Object obj, Class<V> clazz) {
|
||||
if (clazz.isInstance(obj)) {
|
||||
return Optional.of(clazz.cast(obj));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
else
|
||||
return Optional.of((V) obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,20 +240,6 @@ public final class Lang {
|
||||
return Optional.ofNullable(list.get(index));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value to which the specific key is mapped,
|
||||
* or {@code null} if {@code map} has no mapping for the key
|
||||
* or the value is not in the type of {@code clazz}.
|
||||
*
|
||||
* @param map the map
|
||||
* @param key the key for finding the associate value.
|
||||
* @param <V> the type of values in {@code map}
|
||||
* @return the value to which the specific key is mapped, or {@code null} if {@code map} has no mapping for the key or the type is not correct.
|
||||
*/
|
||||
public static <V> Optional<V> get(Map<?, ?> map, Object key, Class<V> clazz) {
|
||||
return cast(map.get(key), clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* Join two collections into one list.
|
||||
*
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -37,12 +38,12 @@ public final class Logging {
|
||||
LOG = Logger.getLogger("HMCL");
|
||||
}
|
||||
|
||||
public static void start() {
|
||||
public static void start(File logFolder) {
|
||||
LOG.setLevel(Level.FINER);
|
||||
LOG.setUseParentHandlers(false);
|
||||
|
||||
try {
|
||||
FileHandler fileHandler = new FileHandler("hmcl.log");
|
||||
FileHandler fileHandler = new FileHandler(new File(logFolder, "hmcl.log").getAbsolutePath());
|
||||
fileHandler.setLevel(Level.FINEST);
|
||||
fileHandler.setFormatter(DefaultFormatter.INSTANCE);
|
||||
LOG.addHandler(fileHandler);
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.*;
|
||||
|
||||
/**
|
||||
@@ -86,7 +87,7 @@ public final class NetworkUtils {
|
||||
}
|
||||
|
||||
public static String doPost(URL url, String post, String contentType, Proxy proxy) throws IOException {
|
||||
byte[] bytes = post.getBytes(Charsets.UTF_8);
|
||||
byte[] bytes = post.getBytes(UTF_8);
|
||||
|
||||
HttpURLConnection con = createConnection(url, proxy);
|
||||
con.setRequestMethod("POST");
|
||||
@@ -108,12 +109,12 @@ public final class NetworkUtils {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = con.getInputStream();
|
||||
return IOUtils.readFullyAsString(is, Charsets.UTF_8);
|
||||
return IOUtils.readFullyAsString(is, UTF_8);
|
||||
} catch (IOException e) {
|
||||
IOUtils.closeQuietly(is);
|
||||
is = con.getErrorStream();
|
||||
if (is != null)
|
||||
return IOUtils.readFullyAsString(is, Charsets.UTF_8);
|
||||
return IOUtils.readFullyAsString(is, UTF_8);
|
||||
throw e;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
|
||||
@@ -20,10 +20,14 @@ package org.jackhuang.hmcl.util;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
|
||||
import javax.management.JMException;
|
||||
import javax.management.MBeanServer;
|
||||
import javax.management.ObjectName;
|
||||
import java.io.File;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the operating system.
|
||||
@@ -103,11 +107,9 @@ public enum OperatingSystem {
|
||||
else
|
||||
CURRENT_OS = UNKNOWN;
|
||||
|
||||
Object bytes = ReflectionHelper.call(ManagementFactory.getOperatingSystemMXBean(), "getTotalPhysicalMemorySize");
|
||||
if (bytes instanceof Long)
|
||||
TOTAL_MEMORY = (int) (((Long) bytes) / 1024 / 1024);
|
||||
else
|
||||
TOTAL_MEMORY = 1024;
|
||||
TOTAL_MEMORY = getTotalPhysicalMemorySize()
|
||||
.map(bytes -> (int) (bytes / 1024 / 1024))
|
||||
.orElse(1024);
|
||||
|
||||
SUGGESTED_MEMORY = (int) (Math.round(1.0 * TOTAL_MEMORY / 4.0 / 128.0) * 128);
|
||||
|
||||
@@ -117,6 +119,19 @@ public enum OperatingSystem {
|
||||
SYSTEM_ARCHITECTURE = arch;
|
||||
}
|
||||
|
||||
private static Optional<Long> getTotalPhysicalMemorySize() {
|
||||
MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
|
||||
try {
|
||||
Object attribute = mBeanServer.getAttribute(new ObjectName("java.lang", "type", "OperatingSystem"), "TotalPhysicalMemorySize");
|
||||
if (attribute instanceof Long) {
|
||||
return Optional.of((Long) attribute);
|
||||
}
|
||||
} catch (JMException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static void setClipboard(String string) {
|
||||
ClipboardContent c = new ClipboardContent();
|
||||
c.putString(string);
|
||||
@@ -128,4 +143,19 @@ public enum OperatingSystem {
|
||||
System.runFinalization();
|
||||
System.gc();
|
||||
}
|
||||
|
||||
public static File getWorkingDirectory(String folder) {
|
||||
String home = System.getProperty("user.home", ".");
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case LINUX:
|
||||
return new File(home, "." + folder + "/");
|
||||
case WINDOWS:
|
||||
String appdata = System.getenv("APPDATA");
|
||||
return new File(Lang.nonNull(appdata, home), "." + folder + "/");
|
||||
case OSX:
|
||||
return new File(home, "Library/Application Support/" + folder);
|
||||
default:
|
||||
return new File(home, folder + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,14 @@ import java.util.Objects;
|
||||
*/
|
||||
public class Pair<K, V> implements Map.Entry<K, V> {
|
||||
|
||||
public static <K, V> Pair<K, V> pair(K key, V value) {
|
||||
return new Pair<>(key, value);
|
||||
}
|
||||
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
@Deprecated
|
||||
public Pair(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
|
||||
@@ -17,136 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ReflectionHelper {
|
||||
|
||||
private static final Unsafe UNSAFE;
|
||||
private static final long OBJECT_FIELD_OFFSET;
|
||||
private static final Map<String, Class<?>> PRIMITIVES;
|
||||
|
||||
static {
|
||||
try {
|
||||
UNSAFE = (Unsafe) AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
|
||||
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
return theUnsafe.get(null);
|
||||
});
|
||||
|
||||
OBJECT_FIELD_OFFSET = UNSAFE.objectFieldOffset(getField(AccessibleObject.class, "override"));
|
||||
|
||||
} catch (PrivilegedActionException ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
|
||||
PRIMITIVES = Lang.mapOf(
|
||||
new Pair<>("byte", Byte.class),
|
||||
new Pair<>("short", Short.class),
|
||||
new Pair<>("int", Integer.class),
|
||||
new Pair<>("long", Long.class),
|
||||
new Pair<>("char", Character.class),
|
||||
new Pair<>("float", Float.class),
|
||||
new Pair<>("double", Double.class),
|
||||
new Pair<>("boolean", Boolean.class)
|
||||
);
|
||||
}
|
||||
|
||||
private static void setAccessibleForcibly(AccessibleObject object) {
|
||||
UNSAFE.putBoolean(object, OBJECT_FIELD_OFFSET, true);
|
||||
}
|
||||
|
||||
public static Method getMethod(Object object, String name) {
|
||||
return getMethod(object.getClass(), name);
|
||||
}
|
||||
|
||||
public static Method getMethod(Class<?> clazz, String name) {
|
||||
try {
|
||||
Method m = clazz.getDeclaredMethod(name);
|
||||
setAccessibleForcibly(m);
|
||||
return m;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Field getField(Object object, String name) {
|
||||
return getField(object.getClass(), name);
|
||||
}
|
||||
|
||||
public static Field getField(Class<?> clazz, String name) {
|
||||
try {
|
||||
Field f = clazz.getDeclaredField(name);
|
||||
setAccessibleForcibly(f);
|
||||
return f;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object call(Class<?> cls, String name, Object object, Object... args) {
|
||||
try {
|
||||
if (args.length == 0)
|
||||
try {
|
||||
return cls.getDeclaredField(name).get(object);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
if ("new".equals(name)) {
|
||||
for (Constructor<?> c : cls.getDeclaredConstructors())
|
||||
if (checkParameter(c, args))
|
||||
return c.newInstance(args);
|
||||
} else
|
||||
return forMethod(cls, name, args).get().invoke(object, args);
|
||||
throw new RuntimeException();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Cannot find '" + name + "' in class '" + cls.getName() + "'", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object construct(Class<?> clazz, Object... args) {
|
||||
return call(clazz, "new", null, args);
|
||||
}
|
||||
|
||||
public static Object call(Object obj, String name, Object... args) {
|
||||
return call(obj.getClass(), name, obj, args);
|
||||
}
|
||||
|
||||
public static boolean checkParameter(Executable exec, Object... args) {
|
||||
Class<?>[] cArgs = exec.getParameterTypes();
|
||||
if (args.length == cArgs.length) {
|
||||
for (int i = 0; i < args.length; ++i) {
|
||||
Object arg = args[i];
|
||||
if (arg != null ? !isInstance(cArgs[i], arg) : cArgs[i].isPrimitive())
|
||||
return false;
|
||||
}
|
||||
setAccessibleForcibly(exec);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean isInstance(Class<?> superClass, Object obj) {
|
||||
return superClass.isInstance(obj) || PRIMITIVES.get(superClass.getName()) == obj.getClass();
|
||||
}
|
||||
|
||||
public static Optional<Method> forMethod(Class<?> cls, String name, Object... args) {
|
||||
return Arrays.stream(cls.getDeclaredMethods())
|
||||
.filter(s -> name.equals(s.getName()))
|
||||
.filter(s -> checkParameter(s, args))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public static StackTraceElement getCaller() {
|
||||
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
|
||||
StackTraceElement caller = elements[2];
|
||||
|
||||
@@ -50,6 +50,7 @@ allprojects {
|
||||
compile "com.google.code.gson:gson:2.8.2"
|
||||
compile "org.apache.commons:commons-compress:1.15"
|
||||
compile "org.tukaani:xz:1.2"
|
||||
compile "org.hildan.fxgson:fx-gson:3.1.0"
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
#Wed Jun 06 15:09:11 CST 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip
|
||||
Binary file not shown.
+1
-1
@@ -1,4 +1,4 @@
|
||||
rootProject.name = 'HMCLKotlin'
|
||||
rootProject.name = 'HMCL3'
|
||||
include 'HMCL'
|
||||
include 'HMCLCore'
|
||||
|
||||
Reference in new issue
Block a user