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 | ||
|
|
663e1ef7ab | ||
|
|
1a47d355f0 | ||
|
|
e407994103 | ||
|
|
bda2149c02 | ||
|
|
b925423d61 | ||
|
|
f3906a37bf | ||
|
|
a1e115ea6b | ||
|
|
03383510ef | ||
|
|
e8cf9644eb | ||
|
|
dbfcf1facc | ||
|
|
44aff94937 | ||
|
|
fb1fc92b7c | ||
|
|
71cfa64b16 | ||
|
|
b200105a85 | ||
|
|
ce83f3c5e1 | ||
|
|
57d3275856 | ||
|
|
eb1a109afc | ||
|
|
a03e3fea12 | ||
|
|
c60ae111ef | ||
|
|
5eb46395ae | ||
|
|
db08b61cfa | ||
|
|
41d313baff | ||
|
|
58c3ed9254 | ||
|
|
affe36a568 | ||
|
|
9ba6cddde7 | ||
|
|
5656c9f351 | ||
|
|
ecf24a746e | ||
|
|
5b5a58a867 | ||
|
|
76c254ee98 |
No files matched your search
+6
-6
@@ -18,7 +18,7 @@ if (buildnumber == null)
|
||||
|
||||
def versionroot = System.getenv("VERSION_ROOT")
|
||||
if (versionroot == null)
|
||||
versionroot = "3.0"
|
||||
versionroot = "3.1"
|
||||
|
||||
String mavenVersion = versionroot + '.' + buildnumber
|
||||
version = mavenVersion
|
||||
@@ -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,12 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
@@ -49,6 +54,14 @@ public final class Launcher extends Application {
|
||||
Controllers.initialize(primaryStage);
|
||||
primaryStage.setResizable(false);
|
||||
primaryStage.setScene(Controllers.getScene());
|
||||
|
||||
UPDATE_CHECKER.process(false)
|
||||
.then(Task.of(Schedulers.javafx(), () -> {
|
||||
if (UPDATE_CHECKER.isOutOfDate())
|
||||
Controllers.showUpdate();
|
||||
}))
|
||||
.start();
|
||||
|
||||
primaryStage.show();
|
||||
} catch (Throwable e) {
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
@@ -58,20 +71,18 @@ 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(LOG_DIRECTORY);
|
||||
|
||||
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args));
|
||||
|
||||
Logging.LOG.info("*** " + TITLE + " ***");
|
||||
|
||||
UPDATE_CHECKER.process(false)
|
||||
.then(Task.of(Schedulers.javafx(), () -> {
|
||||
if (UPDATE_CHECKER.isOutOfDate())
|
||||
Controllers.showUpdate();
|
||||
}))
|
||||
.start();
|
||||
|
||||
launch(args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
@@ -79,42 +90,49 @@ public final class Launcher extends Application {
|
||||
}
|
||||
|
||||
public static void stopApplication() {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
stopWithoutPlatform();
|
||||
Platform.exit();
|
||||
});
|
||||
}
|
||||
|
||||
public static void stopWithoutPlatform() {
|
||||
Logging.LOG.info("Stopping application without JavaFX Toolkit");
|
||||
Logging.LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
return;
|
||||
Controllers.getStage().close();
|
||||
|
||||
Logging.LOG.info("Shutting down executor services.");
|
||||
Schedulers.shutdown();
|
||||
|
||||
Controllers.shutdown();
|
||||
|
||||
Platform.exit();
|
||||
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
|
||||
});
|
||||
}
|
||||
|
||||
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 void stopWithoutPlatform() {
|
||||
Logging.LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
return;
|
||||
Controllers.getStage().close();
|
||||
Schedulers.shutdown();
|
||||
Controllers.shutdown();
|
||||
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
|
||||
});
|
||||
}
|
||||
|
||||
public static List<File> getCurrentJarFiles() {
|
||||
List<File> result = new LinkedList<>();
|
||||
if (Launcher.class.getClassLoader() instanceof URLClassLoader) {
|
||||
URL[] urls = ((URLClassLoader) Launcher.class.getClassLoader()).getURLs();
|
||||
for (URL u : urls)
|
||||
try {
|
||||
File f = new File(u.toURI());
|
||||
if (f.isFile() && (f.getName().endsWith(".exe") || f.getName().endsWith(".jar")))
|
||||
result.add(f);
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty())
|
||||
return null;
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String i18n(String key) {
|
||||
@@ -130,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";
|
||||
@@ -141,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) {
|
||||
String currentDirectory = new File("").getAbsolutePath();
|
||||
Logging.LOG.info("Current directory: " + currentDirectory);
|
||||
if (currentDirectory.contains("!")) {
|
||||
Logging.LOG.severe("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);
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
private static void checkDirectoryPath() {
|
||||
String currentDirectory = new File("").getAbsolutePath();
|
||||
if (currentDirectory.contains("!")) {
|
||||
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
|
||||
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 <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* This event gets fired when added accounts.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class AccountAddedEvent extends Event {
|
||||
|
||||
private final Account account;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public AccountAddedEvent(Object source, Account account) {
|
||||
super(source);
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("account", account)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This event gets fired when loading accounts.
|
||||
* <br>
|
||||
@@ -26,14 +32,29 @@ package org.jackhuang.hmcl.event;
|
||||
*/
|
||||
public class AccountLoadingEvent extends Event {
|
||||
|
||||
private final Collection<Account> accounts;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public AccountLoadingEvent(Object source) {
|
||||
public AccountLoadingEvent(Object source, Collection<Account> accounts) {
|
||||
super(source);
|
||||
this.accounts = Collections.unmodifiableCollection(accounts);
|
||||
}
|
||||
|
||||
|
||||
public Collection<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("accounts", accounts)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
/**
|
||||
@@ -42,4 +43,11 @@ public final class OutOfDateEvent extends Event {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", getSource())
|
||||
.append("version", getVersion())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
/**
|
||||
* This event gets fired when the selected profile changed.
|
||||
@@ -44,4 +45,11 @@ public final class ProfileChangedEvent extends Event {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("profile", profile)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.event;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This event gets fired when loading profiles.
|
||||
* <br>
|
||||
@@ -26,14 +32,29 @@ package org.jackhuang.hmcl.event;
|
||||
*/
|
||||
public class ProfileLoadingEvent extends Event {
|
||||
|
||||
private final Collection<Profile> profiles;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
public ProfileLoadingEvent(Object source) {
|
||||
public ProfileLoadingEvent(Object source, Collection<Profile> profiles) {
|
||||
super(source);
|
||||
|
||||
this.profiles = Collections.unmodifiableCollection(profiles);
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("source", source)
|
||||
.append("profiles", profiles)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -173,10 +172,20 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return new File(getVersionRoot(id), "icon.png");
|
||||
}
|
||||
|
||||
public void saveVersionSetting(String id) {
|
||||
public boolean saveVersionSetting(String id) {
|
||||
if (!versionSettings.containsKey(id))
|
||||
return;
|
||||
Lang.invoke(() -> FileUtils.writeText(getVersionSettingFile(id), GSON.toJson(versionSettings.get(id))));
|
||||
return false;
|
||||
File file = getVersionSettingFile(id);
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
return false;
|
||||
|
||||
try {
|
||||
FileUtils.writeText(file, GSON.toJson(versionSettings.get(id)));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean forbidsVersion(String id) {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
@@ -25,6 +24,7 @@ import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.ServerDisconnectException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
@@ -36,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.*;
|
||||
|
||||
@@ -46,13 +45,15 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class LauncherHelper {
|
||||
public static final LauncherHelper INSTANCE = new LauncherHelper();
|
||||
private LauncherHelper(){}
|
||||
|
||||
private TaskExecutor executor;
|
||||
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(() -> {});
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
|
||||
|
||||
public void launch(Profile profile, Account account, String selectedVersion, File scriptFile) {
|
||||
if (account == null)
|
||||
@@ -65,7 +66,10 @@ public final class LauncherHelper {
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
checkGameState(profile, setting, version, () -> Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile)));
|
||||
checkGameState(profile, setting, version, () -> {
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile));
|
||||
});
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
});
|
||||
@@ -74,12 +78,11 @@ public final class LauncherHelper {
|
||||
private void launch0(Profile profile, Account account, String selectedVersion, File scriptFile) {
|
||||
GameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = repository.getResolvedVersion(selectedVersion);
|
||||
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
|
||||
VersionSetting setting = profile.getVersionSetting(selectedVersion);
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = this.executor = Task.of(Schedulers.javafx(), () -> Controllers.dialog(launchingStepsPane))
|
||||
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)))
|
||||
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.then(variables -> {
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
@@ -94,20 +97,23 @@ 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));
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(launchingStepsPane));
|
||||
}
|
||||
}))
|
||||
.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 -> {
|
||||
@@ -128,13 +134,15 @@ public final class LauncherHelper {
|
||||
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
|
||||
Launcher.stopApplication();
|
||||
else
|
||||
launchingStepsPane.setCancel(() -> {
|
||||
launchingStepsPane.setCancel(it -> {
|
||||
process.stop();
|
||||
Controllers.closeDialog();
|
||||
Controllers.closeDialog(it);
|
||||
});
|
||||
} else
|
||||
Platform.runLater(() ->
|
||||
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath())));
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
|
||||
}))
|
||||
.executor();
|
||||
@@ -154,16 +162,14 @@ public final class LauncherHelper {
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
if (!success) {
|
||||
Platform.runLater(() -> {
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
if (executor.getLastException() != null)
|
||||
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
|
||||
scriptFile == null ? Launcher.i18n("launch.failed") : Launcher.i18n("version.launch_script.failed"),
|
||||
MessageBox.ERROR_MESSAGE, Controllers::closeDialog);
|
||||
else
|
||||
Controllers.closeDialog();
|
||||
MessageBox.ERROR_MESSAGE);
|
||||
});
|
||||
}
|
||||
|
||||
LauncherHelper.this.executor = null;
|
||||
launchingStepsPane.setExecutor(null);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -171,47 +177,44 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
|
||||
boolean flag = false, suggest = true;
|
||||
boolean flag = false;
|
||||
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
|
||||
JavaVersion java = setting.getJavaVersion();
|
||||
if (java == null) {
|
||||
Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (java.getParsedVersion() < JavaVersion.JAVA_8) {
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.java9"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
suggest = false;
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.different_platform"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
if (OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(Launcher.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
if (suggest && Controllers.getDialogContent() instanceof MessageDialogPane)
|
||||
((MessageDialogPane) Controllers.getDialogContent()).disableClosingDialog();
|
||||
} else
|
||||
if (!flag)
|
||||
onAccept.run();
|
||||
}
|
||||
|
||||
@@ -221,10 +224,11 @@ public final class LauncherHelper {
|
||||
}
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE)
|
||||
Controllers.closeDialog(launchingStepsPane);
|
||||
|
||||
launchingStepsPane.setTitle(state.getLocalizedMessage());
|
||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
}
|
||||
|
||||
private void checkExit(LauncherVisibility v) {
|
||||
@@ -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();
|
||||
|
||||
@@ -359,7 +363,9 @@ public final class LauncherHelper {
|
||||
// Never come to here.
|
||||
break;
|
||||
case KEEP:
|
||||
// No operations here
|
||||
Platform.runLater(() -> {
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
|
||||
@@ -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,274 +17,93 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.*;
|
||||
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;
|
||||
|
||||
final class Config {
|
||||
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 final ObservableSet<String> authlibInjectorServerURLs = FXCollections.observableSet(new HashSet<>());
|
||||
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public void setSelectedProfile(String selectedProfile) {
|
||||
this.selectedProfile = selectedProfile;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage = backgroundImage;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getBackgroundImageType() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(int backgroundImageType) {
|
||||
this.backgroundImageType = backgroundImageType;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
return commonDirectory;
|
||||
}
|
||||
|
||||
public void setCommonDirectory(String commonDirectory) {
|
||||
this.commonDirectory = commonDirectory;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public boolean isHasProxy() {
|
||||
return hasProxy;
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
this.hasProxy = hasProxy;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public boolean isHasProxyAuth() {
|
||||
return hasProxyAuth;
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
this.hasProxyAuth = hasProxyAuth;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getProxyType() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public void setProxyType(int proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
this.proxyHost = proxyHost;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
this.proxyPort = proxyPort;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return proxyUser;
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
this.proxyUser = proxyUser;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return proxyPass;
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
this.proxyPass = proxyPass;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getTheme() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public void setTheme(String theme) {
|
||||
this.theme = theme;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public List<JavaVersion> getJava() {
|
||||
return java;
|
||||
}
|
||||
|
||||
public void setJava(List<JavaVersion> java) {
|
||||
this.java = java;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public void setLocalization(String localization) {
|
||||
this.localization = localization;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadType) {
|
||||
this.downloadType = downloadType;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public Map<String, Profile> getConfigurations() {
|
||||
return configurations;
|
||||
}
|
||||
|
||||
public void setConfigurations(Map<String, Profile> configurations) {
|
||||
this.configurations = configurations;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public List<Map<Object, Object>> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public void setAccounts(List<Map<Object, Object>> accounts) {
|
||||
this.accounts = accounts;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getSelectedAccount() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public void setSelectedAccount(String selectedAccount) {
|
||||
this.selectedAccount = selectedAccount;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily = fontFamily;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public void setFontSize(double fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
Settings.INSTANCE.save();
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return logLines;
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
this.logLines = logLines;
|
||||
}
|
||||
|
||||
public Set<String> getAuthlibInjectorServerURLs() {
|
||||
return authlibInjectorServerURLs;
|
||||
}
|
||||
|
||||
public void setAuthlibInjectorServerURLs(Set<String> authlibInjectorServerURLs) {
|
||||
this.authlibInjectorServerURLs = authlibInjectorServerURLs;
|
||||
Settings.INSTANCE.save();
|
||||
@Override
|
||||
public Config clone() {
|
||||
return Settings.GSON.fromJson(Settings.GSON.toJson(this), Config.class);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ import java.util.List;
|
||||
public final class DownloadProviders {
|
||||
private DownloadProviders() {}
|
||||
|
||||
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(Settings.INSTANCE.getLocale() == Locales.ZH_CN), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
|
||||
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
|
||||
|
||||
public static DownloadProvider getDownloadProvider(int index) {
|
||||
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(DOWNLOAD_PROVIDERS.get(0));
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.util.ImmediateObjectProperty;
|
||||
import org.jackhuang.hmcl.util.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Type;
|
||||
@@ -146,6 +147,14 @@ public final class Profile {
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("gameDir", getGameDir())
|
||||
.append("name", getName())
|
||||
.toString();
|
||||
}
|
||||
|
||||
public void addPropertyChangedListener(InvalidationListener listener) {
|
||||
nameProperty.addListener(listener);
|
||||
globalProperty.addListener(listener);
|
||||
|
||||
@@ -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,35 +47,42 @@ 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";
|
||||
|
||||
public static final File SETTINGS_FILE = new File("hmcl.json").getAbsoluteFile();
|
||||
public static final String SETTINGS_FILE_NAME = "hmcl.json";
|
||||
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
|
||||
|
||||
public static final Settings INSTANCE = new Settings();
|
||||
|
||||
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();
|
||||
@@ -89,7 +101,6 @@ public class Settings {
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
checkAuthlibInjectorServerURLs();
|
||||
checkAuthlibInjectorAccounts();
|
||||
checkProfileMap();
|
||||
|
||||
@@ -125,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));
|
||||
@@ -138,12 +149,13 @@ 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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -159,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;
|
||||
@@ -167,7 +179,8 @@ public class Settings {
|
||||
|
||||
public void setLocale(Locales.SupportedLocale locale) {
|
||||
this.locale = locale;
|
||||
SETTINGS.setLocalization(Locales.getNameByLocale(locale));
|
||||
SETTINGS.localization.set(Locales.getNameByLocale(locale));
|
||||
save();
|
||||
}
|
||||
|
||||
private Proxy proxy = Proxy.NO_PROXY;
|
||||
@@ -176,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;
|
||||
@@ -184,49 +197,64 @@ 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); }
|
||||
public void setHasProxy(boolean 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); }
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
SETTINGS.hasProxyAuth.set(hasProxyAuth);
|
||||
save();
|
||||
}
|
||||
|
||||
private void loadProxy() {
|
||||
String host = getProxyHost();
|
||||
@@ -255,20 +283,22 @@ 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();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
@@ -276,35 +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<>());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -315,21 +336,22 @@ 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();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* 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();
|
||||
@@ -351,7 +373,8 @@ public class Settings {
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setSelectedAccount(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
SETTINGS.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -370,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) {
|
||||
@@ -398,12 +423,13 @@ 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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -419,12 +445,13 @@ public class Settings {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.getBackgroundImageType())) {
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.backgroundImageType.get())) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setBackgroundImageType(get().ordinal());
|
||||
SETTINGS.backgroundImageType.set(get().ordinal());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -444,12 +471,13 @@ public class Settings {
|
||||
* THEME *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.getTheme()).orElse(Theme.BLUE)) {
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.theme.get()).orElse(Theme.BLUE)) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
SETTINGS.setTheme(get().getName().toLowerCase());
|
||||
SETTINGS.theme.set(get().getName().toLowerCase());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -472,16 +500,20 @@ public class Settings {
|
||||
public Profile getSelectedProfile() {
|
||||
checkProfileMap();
|
||||
|
||||
if (!hasProfile(SETTINGS.getSelectedProfile())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(SETTINGS::setSelectedProfile);
|
||||
if (!hasProfile(SETTINGS.selectedProfile.get())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(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);
|
||||
}
|
||||
}
|
||||
@@ -498,7 +530,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
return SETTINGS.getConfigurations();
|
||||
return SETTINGS.configurations;
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
@@ -535,7 +567,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
private void onProfileChanged() {
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile()));
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(this, getSelectedProfile()));
|
||||
getSelectedProfile().getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
@@ -548,11 +580,15 @@ public class Settings {
|
||||
* Invoked by loading GUI phase.
|
||||
*/
|
||||
public void onProfileLoading() {
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(SETTINGS));
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(this, getProfiles()));
|
||||
onProfileChanged();
|
||||
}
|
||||
|
||||
public void onAccountLoading() {
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountLoadingEvent(SETTINGS));
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountLoadingEvent(this, getAccounts()));
|
||||
}
|
||||
|
||||
public Config getRawConfig() {
|
||||
return SETTINGS.clone();
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,9 @@ import org.jackhuang.hmcl.util.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -87,6 +89,20 @@ public final class VersionSetting {
|
||||
javaProperty.set(java);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty defaultJavaPathProperty = new ImmediateStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
* Path to Java executable, or null if user customizes java directory.
|
||||
* It's used to determine which JRE to use when multiple JREs match the selected Java version.
|
||||
*/
|
||||
public String getDefaultJavaPath() {
|
||||
return defaultJavaPathProperty.get();
|
||||
}
|
||||
|
||||
public void setDefaultJavaPath(String defaultJavaPath) {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty javaDirProperty = new ImmediateStringProperty(this, "javaDir", "");
|
||||
|
||||
public ImmediateStringProperty javaDirProperty() {
|
||||
@@ -444,12 +460,18 @@ public final class VersionSetting {
|
||||
return null; // Custom Java Directory not found,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
JavaVersion c = JavaVersion.getJREs().get(getJava());
|
||||
if (c == null) {
|
||||
List<JavaVersion> matchedJava = JavaVersion.getJREs().stream()
|
||||
.filter(java -> java.getVersion().equals(getJava()))
|
||||
.collect(Collectors.toList());
|
||||
if (matchedJava.isEmpty()) {
|
||||
setJava("Default");
|
||||
return JavaVersion.fromCurrentEnvironment();
|
||||
} else
|
||||
return c;
|
||||
} else {
|
||||
return matchedJava.stream()
|
||||
.filter(java -> java.getBinary().toString().equals(getDefaultJavaPath()))
|
||||
.findFirst()
|
||||
.orElse(matchedJava.get(0));
|
||||
}
|
||||
} else throw new Error();
|
||||
}
|
||||
|
||||
@@ -475,6 +497,7 @@ public final class VersionSetting {
|
||||
gameDirTypeProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
}
|
||||
|
||||
public LaunchOptions toLaunchOptions(File gameDir) throws InterruptedException {
|
||||
@@ -535,6 +558,7 @@ public final class VersionSetting {
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
|
||||
obj.addProperty("defaultJavaPath", src.getDefaultJavaPath());
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -571,6 +595,7 @@ public final class VersionSetting {
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(1)]);
|
||||
vs.setGameDirType(EnumGameDirectory.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
@@ -35,18 +35,19 @@ public class AccountLoginPane extends StackPane {
|
||||
private final Account oldAccount;
|
||||
private final Consumer<AuthInfo> success;
|
||||
private final Runnable failed;
|
||||
private final Consumer<Region> closeConsumer;
|
||||
|
||||
@FXML
|
||||
private Label lblUsername;
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
private JFXDialog dialog;
|
||||
|
||||
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
public AccountLoginPane(Account oldAccount, Consumer<Region> closeConsumer, Consumer<AuthInfo> success, Runnable failed) {
|
||||
this.oldAccount = oldAccount;
|
||||
this.success = success;
|
||||
this.failed = failed;
|
||||
this.closeConsumer = closeConsumer;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
|
||||
|
||||
@@ -69,9 +70,9 @@ public class AccountLoginPane extends StackPane {
|
||||
Object account = variable.get("login");
|
||||
if (account instanceof AuthInfo) {
|
||||
success.accept(((AuthInfo) account));
|
||||
dialog.close();
|
||||
closeConsumer.accept(this);
|
||||
} else if (account instanceof NoSelectedCharacterException) {
|
||||
dialog.close();
|
||||
closeConsumer.accept(this);
|
||||
} else if (account instanceof Exception) {
|
||||
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
|
||||
}
|
||||
@@ -83,10 +84,6 @@ public class AccountLoginPane extends StackPane {
|
||||
@FXML
|
||||
private void onCancel() {
|
||||
failed.run();
|
||||
dialog.close();
|
||||
}
|
||||
|
||||
public void setDialog(JFXDialog dialog) {
|
||||
this.dialog = dialog;
|
||||
closeConsumer.accept(this);
|
||||
}
|
||||
}
|
||||
@@ -18,10 +18,14 @@
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
@@ -37,9 +41,12 @@ import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class AccountPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title;
|
||||
|
||||
private final ObjectProperty<Runnable> onDelete = new SimpleObjectProperty<>(this, "onDelete");
|
||||
private final VersionListItem item;
|
||||
private final Account account;
|
||||
|
||||
@FXML
|
||||
@@ -60,9 +67,13 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
private JFXButton btnDelete;
|
||||
@FXML
|
||||
private JFXButton btnRefresh;
|
||||
@FXML
|
||||
private JFXProgressBar progressBar;
|
||||
|
||||
public AccountPage(Account account) {
|
||||
public AccountPage(Account account, VersionListItem item) {
|
||||
this.account = account;
|
||||
this.item = item;
|
||||
|
||||
title = new SimpleStringProperty(this, "title", Launcher.i18n("account") + " - " + account.getCharacter());
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
|
||||
@@ -95,13 +106,23 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
Settings.INSTANCE.deleteAccount(account);
|
||||
Controllers.navigate(null);
|
||||
Optional.ofNullable(onDelete.get()).ifPresent(Runnable::run);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onRefresh() {
|
||||
if (account instanceof YggdrasilAccount)
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account).start();
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
progressBar.setVisible(true);
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
|
||||
progressBar.setVisible(false);
|
||||
|
||||
if (isDependentsSucceeded) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
@@ -116,4 +137,16 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public Runnable getOnDelete() {
|
||||
return onDelete.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Runnable> onDeleteProperty() {
|
||||
return onDelete;
|
||||
}
|
||||
|
||||
public void setOnDelete(Runnable onDelete) {
|
||||
this.onDelete.set(onDelete);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
@@ -40,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;
|
||||
@@ -49,6 +52,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -61,17 +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;
|
||||
private final Runnable finalization;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private StackPane acceptPane;
|
||||
@FXML private JFXSpinner spinnerAccept;
|
||||
private final Consumer<Region> finalization;
|
||||
private final TransitionHandler transitionHandler;
|
||||
|
||||
public AddAccountPane(Runnable finalization) {
|
||||
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"));
|
||||
@@ -79,8 +89,9 @@ 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);
|
||||
|
||||
@@ -92,6 +103,12 @@ public class AddAccountPane extends StackPane {
|
||||
txtUsername.setOnAction(e -> onCreationAccept());
|
||||
txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
|
||||
txtUsername.textProperty().addListener(it -> validateAcceptButton());
|
||||
txtPassword.textProperty().addListener(it -> validateAcceptButton());
|
||||
}
|
||||
|
||||
private void validateAcceptButton() {
|
||||
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
|
||||
}
|
||||
|
||||
private void loadServers() {
|
||||
@@ -112,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;
|
||||
@@ -132,26 +157,26 @@ 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);
|
||||
finalization.run();
|
||||
hideSpinner();
|
||||
finalization.accept(this);
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
finalization.run();
|
||||
finalization.accept(this);
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
progressBar.setVisible(false);
|
||||
hideSpinner();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationCancel() {
|
||||
finalization.run();
|
||||
finalization.accept(this);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddInjecterServer() {
|
||||
finalization.run();
|
||||
private void onManageInjecterServers() {
|
||||
finalization.accept(this);
|
||||
Controllers.navigate(Controllers.getServersPage());
|
||||
}
|
||||
|
||||
@@ -198,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,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -115,14 +114,9 @@ public final class Controllers {
|
||||
stage.setTitle(Launcher.TITLE);
|
||||
}
|
||||
|
||||
public static Region getDialogContent() {
|
||||
return decorator.getDialog().getContent();
|
||||
}
|
||||
|
||||
public static JFXDialog dialog(Region content) {
|
||||
// TODO: temp fix
|
||||
decorator.showDialog(new Region());
|
||||
return decorator.showDialog(content);
|
||||
public static void dialog(Region content) {
|
||||
if (decorator != null)
|
||||
decorator.showDialog(content);
|
||||
}
|
||||
|
||||
public static void dialog(String text) {
|
||||
@@ -138,28 +132,33 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, int type, Runnable onAccept) {
|
||||
dialog(new MessageDialogPane(text, title, decorator.getDialog(), type, onAccept));
|
||||
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, type, onAccept));
|
||||
}
|
||||
|
||||
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
|
||||
dialog(new MessageDialogPane(text, title, decorator.getDialog(), onAccept, onCancel));
|
||||
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, onAccept, onCancel));
|
||||
}
|
||||
|
||||
public static void inputDialog(String text, Consumer<String> onResult) {
|
||||
dialog(new InputDialogPane(text, decorator.getDialog(), onResult));
|
||||
dialog(new InputDialogPane(text, Controllers::closeDialog, onResult));
|
||||
}
|
||||
|
||||
public static void taskDialog(TaskExecutor executor, String title, String subtitle, Runnable onCancel) {
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||
return taskDialog(executor, title, subtitle, null);
|
||||
}
|
||||
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
pane.setSubtitle(subtitle);
|
||||
pane.setExecutor(executor);
|
||||
executor.start();
|
||||
dialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static void closeDialog() {
|
||||
decorator.getDialog().close();
|
||||
public static void closeDialog(Region content) {
|
||||
|
||||
decorator.closeDialog(content);
|
||||
}
|
||||
|
||||
public static void navigate(Node node) {
|
||||
@@ -170,6 +169,8 @@ public final class Controllers {
|
||||
}
|
||||
|
||||
public static void showUpdate() {
|
||||
if (stage == null) // shut down
|
||||
return;
|
||||
getLeftPaneController().showUpdate();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ import javafx.scene.shape.Rectangle;
|
||||
import javafx.stage.Screen;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -52,6 +53,7 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
@@ -81,11 +83,13 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
private final boolean max, min;
|
||||
private final WizardController wizardController = new WizardController(this);
|
||||
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
|
||||
private final JFXDialog dialog;
|
||||
|
||||
private double xOffset, yOffset, newX, newY, initX, initY;
|
||||
private boolean allowMove, isDragging, dialogShown, maximized;
|
||||
private boolean allowMove, isDragging, maximized;
|
||||
private BoundingBox originalBox, maximizedBox;
|
||||
private final TransitionHandler animationHandler;
|
||||
private final StackContainerPane dialogPane = new StackContainerPane();
|
||||
|
||||
@FXML
|
||||
private StackPane contentPlaceHolder;
|
||||
@@ -118,8 +122,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
@FXML
|
||||
private JFXHamburger titleBurger;
|
||||
@FXML
|
||||
private JFXDialog dialog;
|
||||
@FXML
|
||||
private JFXButton btnMin;
|
||||
@FXML
|
||||
private JFXButton btnMax;
|
||||
@@ -140,7 +142,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/decorator.fxml");
|
||||
|
||||
onCloseButtonAction = new SimpleObjectProperty<>(this, "onCloseButtonAction", primaryStage::close);
|
||||
onCloseButtonAction = new SimpleObjectProperty<>(this, "onCloseButtonAction", Launcher::stopApplication);
|
||||
|
||||
primaryStage.initStyle(StageStyle.UNDECORATED);
|
||||
btnClose.setGraphic(close);
|
||||
@@ -164,9 +166,11 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
btnMax.fire();
|
||||
});
|
||||
|
||||
dialog = new JFXDialog();
|
||||
dialog.setOverlayClose(false);
|
||||
drawerWrapper.getChildren().add(0, dialog);
|
||||
dialog.setDialogContainer(drawerWrapper);
|
||||
dialog.setOnDialogClosed(e -> dialogShown = false);
|
||||
dialog.setOnDialogOpened(e -> dialogShown = true);
|
||||
dialog.setContent(dialogPane);
|
||||
|
||||
if (!min) buttonsContainer.getChildren().remove(btnMin);
|
||||
if (!max) buttonsContainer.getChildren().remove(btnMax);
|
||||
@@ -187,14 +191,17 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
|
||||
private void loadBackground() {
|
||||
Image background;
|
||||
try {
|
||||
Image background;
|
||||
|
||||
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
|
||||
else
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
|
||||
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
|
||||
else
|
||||
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
|
||||
|
||||
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
|
||||
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private static Image searchBackgroundImage(Image def, String customPath) {
|
||||
@@ -510,6 +517,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
|
||||
public void showPage(Node content) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
|
||||
if (content != null)
|
||||
contentPlaceHolder.getStyleClass().add("gray-background");
|
||||
@@ -530,11 +539,23 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
}
|
||||
|
||||
public JFXDialog showDialog(Region content) {
|
||||
dialog.setContent(content);
|
||||
if (!dialogShown)
|
||||
public void showDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
if (dialogPane.isEmpty())
|
||||
dialog.show();
|
||||
return dialog;
|
||||
dialogPane.push(node);
|
||||
}
|
||||
|
||||
public void closeDialog(Node node) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
dialogPane.pop(node);
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
Platform.runLater(() -> {
|
||||
if (dialogPane.getChildren().isEmpty())
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider) {
|
||||
@@ -542,6 +563,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
}
|
||||
|
||||
public void startWizard(WizardProvider wizardProvider, String category) {
|
||||
FXUtils.checkFxUserThread();
|
||||
|
||||
this.category = category;
|
||||
wizardController.setProvider(wizardProvider);
|
||||
wizardController.onStart();
|
||||
@@ -623,10 +646,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
return wizardController;
|
||||
}
|
||||
|
||||
public JFXDialog getDialog() {
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public AdvancedListBox getLeftPane() {
|
||||
return leftPane;
|
||||
}
|
||||
|
||||
@@ -34,15 +34,11 @@ public final class DialogController {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
JFXUtilities.runInFX(() -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, it -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, Controllers::closeDialog, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
Controllers.closeDialog();
|
||||
}, () -> {
|
||||
latch.countDown();
|
||||
Controllers.closeDialog();
|
||||
});
|
||||
pane.setDialog(Controllers.dialog(pane));
|
||||
}, latch::countDown);
|
||||
Controllers.dialog(pane);
|
||||
});
|
||||
latch.await();
|
||||
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.adapters.ReflectionHelper;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
@@ -46,18 +46,28 @@ 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() {
|
||||
}
|
||||
|
||||
public static void checkFxUserThread() {
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
throw new IllegalStateException("Not on FX application thread; currentThread = "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener((a, b, c) -> consumer.accept(c));
|
||||
}
|
||||
@@ -114,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);
|
||||
@@ -190,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));
|
||||
}
|
||||
@@ -203,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);
|
||||
}
|
||||
@@ -322,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);
|
||||
}
|
||||
@@ -368,6 +377,31 @@ public final class FXUtils {
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T runInUIThread(Supplier<T> supplier) {
|
||||
if (javafx.application.Platform.isFxApplicationThread()) {
|
||||
return supplier.get();
|
||||
} else {
|
||||
CountDownLatch doneLatch = new CountDownLatch(1);
|
||||
AtomicReference<T> reference = new AtomicReference<>();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
reference.set(supplier.get());
|
||||
} finally {
|
||||
doneLatch.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
try {
|
||||
doneLatch.await();
|
||||
} catch (InterruptedException var3) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
return reference.get();
|
||||
}
|
||||
}
|
||||
|
||||
public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png");
|
||||
|
||||
public static final Interpolator SINE = new Interpolator() {
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
@@ -89,7 +90,7 @@ public class InstallerController {
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
|
||||
|
||||
if (!gameVersion.isPresent())
|
||||
Controllers.dialog("version.cannot_read");
|
||||
Controllers.dialog(Launcher.i18n("version.cannot_read"));
|
||||
else
|
||||
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
@@ -32,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;
|
||||
@@ -41,6 +43,7 @@ import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.ClassTitle;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
@@ -48,8 +51,10 @@ 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;
|
||||
|
||||
public final class LeftPaneController {
|
||||
private final AdvancedListBox leftPane;
|
||||
@@ -57,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;
|
||||
@@ -83,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);
|
||||
@@ -141,15 +148,29 @@ 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);
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
JFXPopup popup = new JFXPopup(accountPage);
|
||||
accountPage.setOnDelete(popup::hide);
|
||||
popup.show((Node) e.getSource(), JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
|
||||
});
|
||||
ripplerContainer.setOnMouseClicked(e -> {
|
||||
@@ -163,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)
|
||||
@@ -191,10 +212,10 @@ public final class LeftPaneController {
|
||||
}
|
||||
|
||||
private boolean checkedModpack = false;
|
||||
private static boolean showNewAccount = true;
|
||||
|
||||
private void onRefreshedVersions(RefreshedVersionsEvent event) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
boolean flag = true;
|
||||
HMCLGameRepository repository = (HMCLGameRepository) event.getSource();
|
||||
if (!checkedModpack) {
|
||||
checkedModpack = true;
|
||||
@@ -203,22 +224,26 @@ public final class LeftPaneController {
|
||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||
if (modpackFile.exists()) {
|
||||
try {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
|
||||
Controllers.taskDialog(ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), () -> {
|
||||
Controllers.closeDialog();
|
||||
checkAccount();
|
||||
})).executor(),
|
||||
Launcher.i18n("modpack.installing"), "", null);
|
||||
flag = false;
|
||||
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.with(Task.of(Schedulers.javafx(), () -> {
|
||||
Controllers.closeDialog(region.get());
|
||||
checkAccount();
|
||||
})).executor();
|
||||
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.installing"), ""));
|
||||
executor.start();
|
||||
showNewAccount = false;
|
||||
} catch (UnsupportedModpackException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flag)
|
||||
if (showNewAccount) {
|
||||
showNewAccount = false;
|
||||
checkAccount();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
@@ -41,18 +43,20 @@ 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;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
@@ -70,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");
|
||||
@@ -82,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();
|
||||
@@ -94,28 +101,35 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.installTooltip(btnRefresh, Launcher.i18n("button.refresh"));
|
||||
}
|
||||
|
||||
private Node buildNode(HMCLGameRepository repository, Version version, String game) {
|
||||
private String modifyVersion(String gameVersion, String version) {
|
||||
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
|
||||
}
|
||||
|
||||
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(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)forge", "").replace(game, "").trim(), "-"), "-")).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
|
||||
libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)liteloader", "").replace(game, "").trim(), "-"), "-")).append("\n");
|
||||
}
|
||||
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
|
||||
libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)optifine", "").replace(game, "").trim(), "-"), "-")).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
item.setLibraries(libraries.toString());
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
@@ -127,7 +141,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
Controllers.dialog(Launcher.i18n("login.empty_username"));
|
||||
else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id));
|
||||
if (repository.getRunDirectory(id).isDirectory())
|
||||
chooser.setInitialDirectory(repository.getRunDirectory(id));
|
||||
chooser.setTitle(Launcher.i18n("version.launch_script.save"));
|
||||
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
|
||||
? new FileChooser.ExtensionFilter(Launcher.i18n("extension.bat"), "*.bat")
|
||||
@@ -147,19 +162,20 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
|
||||
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
|
||||
if (selectedFile != null) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(null);
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
try {
|
||||
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
|
||||
.then(Task.of(Schedulers.javafx(), Controllers::closeDialog)).executor();
|
||||
pane.setExecutor(executor);
|
||||
pane.setTitle(Launcher.i18n("modpack.update"));
|
||||
.then(Task.of(Schedulers.javafx(), () -> Controllers.closeDialog(region.get()))).executor();
|
||||
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.update"), ""));
|
||||
executor.start();
|
||||
Controllers.dialog(pane);
|
||||
} catch (UnsupportedModpackException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.unsupported"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (MismatchedModpackTypeException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.mismatched_type"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
} catch (IOException e) {
|
||||
} catch (IOException e) {
|
||||
Controllers.closeDialog(region.get());
|
||||
Controllers.dialog(Launcher.i18n("modpack.invalid"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
@@ -176,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:
|
||||
@@ -192,8 +208,9 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
FXUtils.openFolder(repository.getRunDirectory(id));
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}});
|
||||
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)
|
||||
@@ -210,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,18 +18,16 @@
|
||||
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;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class InputDialogPane extends StackPane {
|
||||
private final String text;
|
||||
private final JFXDialog dialog;
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@@ -40,16 +38,13 @@ public class InputDialogPane extends StackPane {
|
||||
@FXML
|
||||
private Label content;
|
||||
|
||||
public InputDialogPane(String text, JFXDialog dialog, Consumer<String> onResult) {
|
||||
this.text = text;
|
||||
this.dialog = dialog;
|
||||
|
||||
public InputDialogPane(String text, Consumer<Region> closeConsumer, Consumer<String> onResult) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
||||
content.setText(text);
|
||||
cancelButton.setOnMouseClicked(e -> dialog.close());
|
||||
cancelButton.setOnMouseClicked(e -> closeConsumer.accept(this));
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
onResult.accept(textField.getText());
|
||||
dialog.close();
|
||||
closeConsumer.accept(this);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.Optional;
|
||||
@@ -123,30 +124,32 @@ public final class MessageBox {
|
||||
}
|
||||
|
||||
public static int confirm(String message, String title, int option) {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
switch (option) {
|
||||
case YES_NO_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
||||
break;
|
||||
case YES_NO_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
|
||||
break;
|
||||
case OK_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box option " + option);
|
||||
}
|
||||
Optional<ButtonType> buttonType = alert.showAndWait();
|
||||
if (!buttonType.isPresent()) return CLOSED_OPTION;
|
||||
else if (buttonType.get() == ButtonType.OK) return OK_OPTION;
|
||||
else if (buttonType.get() == ButtonType.YES) return YES_OPTION;
|
||||
else if (buttonType.get() == ButtonType.NO) return NO_OPTION;
|
||||
else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION;
|
||||
else throw new IllegalStateException("Unrecognized button type:" + buttonType.get());
|
||||
return FXUtils.runInUIThread(() -> {
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setTitle(title);
|
||||
alert.setHeaderText(title);
|
||||
alert.setContentText(message);
|
||||
switch (option) {
|
||||
case YES_NO_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
|
||||
break;
|
||||
case YES_NO_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
|
||||
break;
|
||||
case OK_CANCEL_OPTION:
|
||||
alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized message box option " + option);
|
||||
}
|
||||
Optional<ButtonType> buttonType = alert.showAndWait();
|
||||
if (!buttonType.isPresent()) return CLOSED_OPTION;
|
||||
else if (buttonType.get() == ButtonType.OK) return OK_OPTION;
|
||||
else if (buttonType.get() == ButtonType.YES) return YES_OPTION;
|
||||
else if (buttonType.get() == ButtonType.NO) return NO_OPTION;
|
||||
else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION;
|
||||
else throw new IllegalStateException("Unexpected button type:" + buttonType.get());
|
||||
});
|
||||
}
|
||||
|
||||
public static Optional<String> input(String message) {
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
@@ -29,6 +29,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class MessageDialogPane extends StackPane {
|
||||
private boolean closingDialog = true;
|
||||
@@ -46,7 +47,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
@FXML
|
||||
private HBox actions;
|
||||
|
||||
public MessageDialogPane(String text, String title, JFXDialog dialog, int type, Runnable onAccept) {
|
||||
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, int type, Runnable onAccept) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
|
||||
|
||||
if (title != null)
|
||||
@@ -54,8 +55,7 @@ public final class MessageDialogPane extends StackPane {
|
||||
|
||||
content.setText(text);
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
if (closingDialog)
|
||||
dialog.close();
|
||||
closeConsumer.accept(MessageDialogPane.this);
|
||||
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
|
||||
});
|
||||
|
||||
@@ -82,13 +82,12 @@ public final class MessageDialogPane extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
public MessageDialogPane(String text, String title, JFXDialog dialog, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, dialog, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, Runnable onAccept, Runnable onCancel) {
|
||||
this(text, title, closeConsumer, MessageBox.QUESTION_MESSAGE, onAccept);
|
||||
|
||||
cancelButton.setVisible(true);
|
||||
cancelButton.setOnMouseClicked(e -> {
|
||||
if (closingDialog)
|
||||
dialog.close();
|
||||
closeConsumer.accept(MessageDialogPane.this);
|
||||
Optional.ofNullable(onCancel).ifPresent(Runnable::run);
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
public class StackContainerPane extends StackPane {
|
||||
private final Stack<Node> stack = new Stack<>();
|
||||
|
||||
public void push(Node node) {
|
||||
stack.push(node);
|
||||
getChildren().setAll(node);
|
||||
|
||||
Logging.LOG.info(this + " " + stack);
|
||||
}
|
||||
|
||||
public void pop(Node node) {
|
||||
boolean flag = stack.remove(node);
|
||||
if (stack.isEmpty())
|
||||
getChildren().setAll();
|
||||
else
|
||||
getChildren().setAll(stack.peek());
|
||||
|
||||
Logging.LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return stack.isEmpty();
|
||||
}
|
||||
}
|
||||
@@ -23,15 +23,17 @@ import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class TaskExecutorDialogPane extends StackPane {
|
||||
private TaskExecutor executor;
|
||||
private Runnable onCancel;
|
||||
private Consumer<Region> onCancel;
|
||||
|
||||
@FXML
|
||||
private JFXProgressBar progressBar;
|
||||
@@ -44,20 +46,22 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
@FXML
|
||||
private TaskListPane taskListPane;
|
||||
|
||||
public TaskExecutorDialogPane(Runnable cancel) {
|
||||
public TaskExecutorDialogPane(Consumer<Region> cancel) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/task-dialog.fxml");
|
||||
|
||||
setCancel(cancel);
|
||||
|
||||
btnCancel.setOnMouseClicked(e -> {
|
||||
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
|
||||
onCancel.run();
|
||||
onCancel.accept(this);
|
||||
});
|
||||
}
|
||||
|
||||
public void setExecutor(TaskExecutor executor) {
|
||||
this.executor = executor;
|
||||
taskListPane.setExecutor(executor);
|
||||
|
||||
if (executor != null)
|
||||
taskListPane.setExecutor(executor);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
@@ -91,7 +95,7 @@ public class TaskExecutorDialogPane extends StackPane {
|
||||
progressBar.setProgress(progress);
|
||||
}
|
||||
|
||||
public void setCancel(Runnable onCancel) {
|
||||
public void setCancel(Consumer<Region> onCancel) {
|
||||
this.onCancel = onCancel;
|
||||
|
||||
JFXUtilities.runInFX(() -> btnCancel.setDisable(onCancel == null));
|
||||
|
||||
+20
-16
@@ -33,8 +33,8 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
|
||||
@Override
|
||||
default void handleTask(Map<String, Object> settings, Task task) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(() -> {
|
||||
Controllers.closeDialog();
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
|
||||
Controllers.closeDialog(it);
|
||||
Controllers.navigate(null);
|
||||
});
|
||||
|
||||
@@ -60,20 +60,24 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
|
||||
TaskExecutor executor = task.executor(new TaskListener() {
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
if (success) {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(Launcher.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
return;
|
||||
String appendix = StringUtils.getStackTrace(executor.getLastException());
|
||||
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Launcher.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
|
||||
}
|
||||
JFXUtilities.runInFX(() -> {
|
||||
Controllers.closeDialog(pane);
|
||||
if (success) {
|
||||
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
|
||||
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
|
||||
else if (!settings.containsKey("forbid_success_message"))
|
||||
Controllers.dialog(Launcher.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
|
||||
} else {
|
||||
if (executor.getLastException() == null)
|
||||
return;
|
||||
String appendix = StringUtils.getStackTrace(executor.getLastException());
|
||||
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
|
||||
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
|
||||
else if (!settings.containsKey("forbid_failure_message"))
|
||||
Controllers.dialog(appendix, Launcher.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
pane.setExecutor(executor);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.validation.base.ValidatorBase;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.TextInputControl;
|
||||
@@ -17,10 +16,6 @@ public class URLValidator extends ValidatorBase {
|
||||
super();
|
||||
}
|
||||
|
||||
public URLValidator(@NamedArg("message") String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ObservableList<String> getProtocols() {
|
||||
return protocols;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -89,10 +89,10 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
chkSnapshot.selectedProperty().addListener(listener);
|
||||
chkOld.selectedProperty().addListener(listener);
|
||||
|
||||
list.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue == null)
|
||||
list.setOnMouseClicked(e -> {
|
||||
if (list.getSelectionModel().getSelectedIndex() < 0)
|
||||
return;
|
||||
controller.getSettings().put(libraryId, newValue.getRemoteVersion());
|
||||
controller.getSettings().put(libraryId, list.getSelectionModel().getSelectedItem().getRemoteVersion());
|
||||
callback.run();
|
||||
});
|
||||
refresh();
|
||||
|
||||
@@ -18,14 +18,22 @@
|
||||
package org.jackhuang.hmcl.ui.export;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
|
||||
import org.jackhuang.hmcl.game.HMCLModpackManager;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.ZipEngine;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -46,16 +54,72 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
public Object finish(Map<String, Object> settings) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> whitelist = (List<String>) settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION);
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
boolean includeLauncher = (Boolean) settings.get(ModpackInfoPage.MODPACK_INCLUDE_LAUNCHER) && launcherJar != null;
|
||||
|
||||
return new HMCLModpackExportTask(profile.getRepository(), version, whitelist,
|
||||
new Modpack(
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
|
||||
null,
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
|
||||
null
|
||||
), (File) settings.get(ModpackInfoPage.MODPACK_FILE));
|
||||
return new Task() {
|
||||
Task dependency = null;
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
File modpackFile = (File) settings.get(ModpackInfoPage.MODPACK_FILE);
|
||||
File tempModpack = includeLauncher ? Files.createTempFile("hmcl", ".zip").toFile() : modpackFile;
|
||||
|
||||
dependency = new HMCLModpackExportTask(profile.getRepository(), version, whitelist,
|
||||
new Modpack(
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
|
||||
null,
|
||||
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
|
||||
null
|
||||
), tempModpack);
|
||||
|
||||
if (includeLauncher) {
|
||||
dependency = dependency.then(Task.of(() -> {
|
||||
boolean flag = true;
|
||||
|
||||
try (ZipEngine zip = new ZipEngine(modpackFile)) {
|
||||
Config config = Settings.INSTANCE.getRawConfig();
|
||||
|
||||
config.hasProxy.set(false);
|
||||
config.selectedProfile.set("");
|
||||
config.commonDirectory.set(null);
|
||||
config.fontFamily.set("Consolas");
|
||||
config.fontSize.set(12);
|
||||
config.localization.set(null);
|
||||
config.accounts.clear();
|
||||
config.selectedAccount.set("");
|
||||
config.logLines.set(100);
|
||||
config.configurations.clear();
|
||||
|
||||
zip.putTextFile(Settings.GSON.toJson(config), Settings.SETTINGS_FILE_NAME);
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
|
||||
File bg = new File("bg").getAbsoluteFile();
|
||||
if (bg.isDirectory())
|
||||
zip.putDirectory(bg);
|
||||
|
||||
File background_png = new File("background.png").getAbsoluteFile();
|
||||
if (background_png.isFile())
|
||||
zip.putFile(background_png, "background.png");
|
||||
|
||||
File background_jpg = new File("background.jpg").getAbsoluteFile();
|
||||
if (background_jpg.isFile())
|
||||
zip.putFile(background_jpg, "background.jpg");
|
||||
|
||||
for (File jar : launcherJar)
|
||||
zip.putFile(jar, jar.getName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return Collections.singleton(dependency);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -66,6 +67,9 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
}
|
||||
|
||||
private CheckBoxTreeItem<String> getTreeItem(File file, String basePath) {
|
||||
if (!file.exists())
|
||||
return null;
|
||||
|
||||
ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;
|
||||
if (basePath.length() > "minecraft/".length()) {
|
||||
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory());
|
||||
@@ -151,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"))
|
||||
);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardPage;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -45,12 +46,16 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
@FXML
|
||||
private JFXTextField txtModpackName;
|
||||
@FXML
|
||||
private JFXTextField txtModpackAuthor;@FXML
|
||||
private JFXTextField txtModpackVersion;@FXML
|
||||
private JFXTextField txtModpackAuthor;
|
||||
@FXML
|
||||
private JFXTextField txtModpackVersion;
|
||||
@FXML
|
||||
private JFXTextArea txtModpackDescription;
|
||||
@FXML
|
||||
private JFXToggleButton chkIncludeLauncher;@FXML
|
||||
private JFXButton btnNext;@FXML
|
||||
private JFXToggleButton chkIncludeLauncher;
|
||||
@FXML
|
||||
private JFXButton btnNext;
|
||||
@FXML
|
||||
private ScrollPane scroll;
|
||||
|
||||
public ModpackInfoPage(WizardController controller, String version) {
|
||||
@@ -63,6 +68,10 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
txtModpackVersion.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackAuthor.setText(Optional.ofNullable(Settings.INSTANCE.getSelectedAccount()).map(Account::getUsername).orElse(""));
|
||||
lblVersionName.setText(version);
|
||||
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
if (launcherJar == null)
|
||||
chkIncludeLauncher.setDisable(true);
|
||||
}
|
||||
|
||||
private void checkValidation() {
|
||||
|
||||
@@ -19,10 +19,12 @@ package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageBox;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
@@ -31,11 +33,10 @@ 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;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
@@ -48,22 +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");
|
||||
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
|
||||
new URLClassLoader(new URL[]{jar.toURI().toURL()},
|
||||
ClassLoader.getSystemClassLoader().getParent()).loadClass(mainClass)
|
||||
.getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
|
||||
return null;
|
||||
});
|
||||
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
|
||||
@@ -79,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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,59 +102,60 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
if (!(ver instanceof IntVersionNumber))
|
||||
return;
|
||||
IntVersionNumber version = (IntVersionNumber) ver;
|
||||
checker.requestDownloadLink().then(Task.of(Schedulers.javafx(), variables -> {
|
||||
checker.requestDownloadLink().then(Task.of(variables -> {
|
||||
Map<String, String> map = variables.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID);
|
||||
|
||||
if (MessageBox.confirm(Launcher.i18n("update.newest_version") + version.toString() + "\n"
|
||||
+ Launcher.i18n("update.should_open_link"),
|
||||
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION)
|
||||
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("jarsha1"))
|
||||
hash = map.get("jarsha1");
|
||||
Controllers.dialog(Launcher.i18n("message.downloading"));
|
||||
if (new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash).test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.closeDialog();
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else if (map != null && map.containsKey("pack") && !StringUtils.isBlank(map.get("pack")))
|
||||
try {
|
||||
String hash = null;
|
||||
if (map.containsKey("packsha1"))
|
||||
hash = map.get("packsha1");
|
||||
Controllers.dialog(Launcher.i18n("message.downloading"));
|
||||
if (new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash).test()) {
|
||||
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
|
||||
.directory(new File("").getAbsoluteFile()).start();
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.closeDialog();
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
|
||||
}
|
||||
else {
|
||||
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());
|
||||
if (url == null)
|
||||
url = Launcher.PUBLISH;
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -186,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);
|
||||
@@ -204,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) {
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.util.Charsets;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
|
||||
@@ -28,8 +31,11 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -52,7 +58,11 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
if (url == null) return;
|
||||
File newf = new File(url.getFile());
|
||||
Controllers.dialog(Launcher.i18n("message.downloading"));
|
||||
if (new FileDownloadTask(url, newf).test()) {
|
||||
Task task = new FileDownloadTask(url, newf);
|
||||
TaskExecutor executor = task.executor();
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
|
||||
if (executor.test()) {
|
||||
try {
|
||||
new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath())
|
||||
.directory(new File("").getAbsoluteFile())
|
||||
@@ -62,7 +72,7 @@ public class NewFileUpgrader extends IUpgrader {
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
Controllers.closeDialog();
|
||||
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
|
||||
}
|
||||
|
||||
private static String getRealPath() {
|
||||
@@ -70,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("http://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("http://huangyuhui.duapp.com/update_link.php?type=hmcl")), 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);
|
||||
}
|
||||
|
||||
@@ -37,10 +37,13 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
private static final HashMap<String, String> SOURCE = new HashMap<String, String>() {
|
||||
{
|
||||
put("javafx.fxml.LoadException", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("Location is not set", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("UnsatisfiedLinkError", Launcher.i18n("crash.user_fault"));
|
||||
put("java.lang.NoClassDefFoundError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.VerifyError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchMethodError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.NoSuchFieldError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("netscape.javascript.JSException", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.IncompatibleClassChangeError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.ClassFormatError", Launcher.i18n("crash.NoClassDefFound"));
|
||||
put("java.lang.OutOfMemoryError", "FUCKING MEMORY LIMIT!");
|
||||
@@ -92,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);
|
||||
@@ -112,7 +115,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
|
||||
map.put("version", Launcher.VERSION);
|
||||
map.put("log", Logging.getLogs());
|
||||
try {
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL("http://huangyuhui.duapp.com/hmcl/crash.php"), map);
|
||||
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/crash.php"), map);
|
||||
if (StringUtils.isNotBlank(response))
|
||||
Logging.LOG.log(Level.SEVERE, "Crash server response: " + response);
|
||||
} catch (IOException ex) {
|
||||
|
||||
@@ -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 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>
|
||||
@@ -26,7 +26,7 @@
|
||||
<actions>
|
||||
<Label fx:id="lblCreationWarning" />
|
||||
<JFXButton onMouseClicked="#onAccept" text="%button.ok" styleClass="dialog-accept"/>
|
||||
<JFXButton onMouseClicked="#finalization" text="%button.cancel" styleClass="dialog-cancel"/>
|
||||
<JFXButton onMouseClicked="#onCancel" text="%button.cancel" styleClass="dialog-cancel"/>
|
||||
</actions>
|
||||
</JFXDialogLayout>
|
||||
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER"/>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import com.jfoenix.controls.JFXProgressBar?>
|
||||
<fx:root xmlns:fx="http://javafx.com/fxml"
|
||||
xmlns="http://javafx.com/javafx"
|
||||
type="StackPane">
|
||||
@@ -77,4 +78,5 @@
|
||||
prefWidth="30" prefHeight="30"/>
|
||||
</right>
|
||||
</BorderPane>
|
||||
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" visible="false" />
|
||||
</fx:root>
|
||||
@@ -20,7 +20,6 @@
|
||||
maxWidth="800">
|
||||
<center>
|
||||
<StackPane fx:id="drawerWrapper" styleClass="jfx-decorator-drawer" FXUtils.overflowHidden="true">
|
||||
<JFXDialog fx:id="dialog" overlayClose="false" />
|
||||
<BorderPane>
|
||||
<left>
|
||||
<StackPane minWidth="200" maxWidth="200" styleClass="jfx-decorator-content-container">
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fitToHeight="true" fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<ScrollPane fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%settings.choose_gamedir"/>
|
||||
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%profile.instance_directory.choose"/>
|
||||
</ComponentList>
|
||||
|
||||
</VBox>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import com.jfoenix.controls.JFXButton?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import org.jackhuang.hmcl.ui.FXUtils?>
|
||||
<fx:root xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
type="StackPane" pickOnBounds="false" FXUtils.limitWidth="160" FXUtils.limitHeight="156">
|
||||
<VBox fx:id="content" pickOnBounds="false">
|
||||
<StackPane fx:id="header" VBox.vgrow="ALWAYS" pickOnBounds="false" style="-fx-background-radius: 2 2 0 0; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;">
|
||||
<VBox style="-fx-padding: 8 8 0 8;">
|
||||
<Label fx:id="lblVersionName" style="-fx-font-size: 15;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblGameVersion" style="-fx-font-size: 11;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
<Label fx:id="lblLibraries" style="-fx-font-size: 10; -fx-text-fill: gray;" textAlignment="JUSTIFY" wrapText="true" />
|
||||
</VBox>
|
||||
</StackPane>
|
||||
<StackPane fx:id="body" style="-fx-background-radius: 0 0 2 2; -fx-background-color: rgb(255,255,255,0.87); -fx-padding: 8;" minHeight="40" pickOnBounds="false">
|
||||
<BorderPane>
|
||||
<left>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnSettings" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnUpdate" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<HBox spacing="8">
|
||||
<JFXButton fx:id="btnScript" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30" prefWidth="30" prefHeight="30" />
|
||||
<JFXButton fx:id="btnLaunch" styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minHeight="30" minWidth="30" prefWidth="30" prefHeight="30" />
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
</StackPane>
|
||||
</VBox>
|
||||
<StackPane fx:id="icon" StackPane.alignment="TOP_RIGHT" pickOnBounds="false">
|
||||
<ImageView fx:id="iconView" StackPane.alignment="CENTER_RIGHT">
|
||||
<StackPane.margin>
|
||||
<Insets right="12" />
|
||||
</StackPane.margin>
|
||||
<Image url="/assets/img/icon.png" />
|
||||
</ImageView>
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -25,7 +25,7 @@
|
||||
<MultiFileItem fx:id="javaItem" title="%settings.game.java_directory" chooserTitle="%settings.game.java_directory.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" directory="false" />
|
||||
|
||||
<MultiFileItem fx:id="gameDirItem" title="%profile.instance_directory" chooserTitle="%settings.choose_gamedir"
|
||||
<MultiFileItem fx:id="gameDirItem" title="%settings.game.game_directory" chooserTitle="%settings.game.game_directory.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" directory="true" />
|
||||
|
||||
<BorderPane> <!-- Max Memory -->
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=Author
|
||||
about.author.statement=huanghongxun (MCF: klkl6523)
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=Thanks to
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
|
||||
about.dependency=Dependency
|
||||
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
|
||||
about.claim=Statement
|
||||
@@ -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
|
||||
@@ -57,8 +58,8 @@ archive.author=Authors
|
||||
archive.game_version=Game
|
||||
archive.version=Version
|
||||
|
||||
assets.download=Download Assets
|
||||
assets.download_all=Download Assets Files
|
||||
assets.download=Download assets
|
||||
assets.download_all=Check for the completion of assets
|
||||
assets.failed=Failed to get the list, try again.
|
||||
assets.failed_download=Failed to download assets, may cause no sounds and language files.
|
||||
|
||||
@@ -106,13 +107,12 @@ crash.advice.no=No advice.
|
||||
crash.advice.no_lwjgl=Maybe drivers caused problems.
|
||||
crash.advice.otherwise=Maybe mods caused problems.
|
||||
crash.error=Minecraft has crashed.
|
||||
crash.headless=If your OS is Linux, please use Oracle JDK instead of OpenJDK, or add "-Djava.awt.headless=false" JVM argument, or check if your Xserver works normally.
|
||||
crash.launcher=Launcher has crashed!
|
||||
crash.minecraft=Minecraft has crashed!
|
||||
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
|
||||
|
||||
download=Download
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi.bangbang93.com/)
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
|
||||
download.failed=Failed to download
|
||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||
download.mojang=Mojang
|
||||
@@ -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
|
||||
@@ -311,6 +310,7 @@ profile=Profile
|
||||
profile.default=Current directory
|
||||
profile.home=User home
|
||||
profile.instance_directory=Game Directory
|
||||
profile.instance_directory.choose=Choose Game Directory
|
||||
profile.new=New Config
|
||||
profile.new_name=New Profile Name:
|
||||
profile.remove=Sure to remove profile %s?
|
||||
@@ -343,7 +343,6 @@ settings.advanced.server_ip=Server Host
|
||||
settings.advanced.wrapper_launcher=Wrapper Launcher(i.e. optirun...)
|
||||
|
||||
settings.cannot_remove_default_config=Cannot remove the default configution.
|
||||
settings.choose_gamedir=Choose Game Directory
|
||||
settings.custom=Custom
|
||||
settings.default=Default
|
||||
settings.failed_load=Failed to load settings file. Remove it?
|
||||
@@ -352,6 +351,8 @@ settings.game=Games
|
||||
settings.game.dimension=Game Window Dimension
|
||||
settings.game.exploration=Explore
|
||||
settings.game.fullscreen=Fullscreen
|
||||
settings.game.game_directory=Game Directory
|
||||
settings.game.game_directory.choose=Choose Game Directory
|
||||
settings.game.java_directory=Java Dir
|
||||
settings.game.java_directory.choose=Choose Java Directory.
|
||||
settings.game.management=Manage
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
about.copyright=版权
|
||||
about.copyright.statement=Copyright (c) 2018 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (百度贴吧:huanghongxun20)
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鸣谢
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
|
||||
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
|
||||
about.dependency=依赖
|
||||
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
|
||||
about.claim=免责声明
|
||||
@@ -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=服务器地址
|
||||
@@ -58,7 +59,7 @@ archive.game_version=游戏版本
|
||||
archive.version=版本
|
||||
|
||||
assets.download=下载资源
|
||||
assets.download_all=下载资源文件
|
||||
assets.download_all=检查资源文件完整性
|
||||
assets.failed=获取列表失败,请刷新重试。
|
||||
assets.failed_download=下载资源文件失败,可能导致没有中文和声音。
|
||||
|
||||
@@ -93,7 +94,7 @@ color.red=红色
|
||||
color.recent=推荐
|
||||
color.custom=自定义颜色
|
||||
|
||||
crash.NoClassDefFound=请确认HMCL本体是否完整
|
||||
crash.NoClassDefFound=请确认HMCL本体是否完整,或更新您的Java。
|
||||
crash.advice.ClassNotFoundException=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题,另可尝试游戏设置->(版本)管理->删除库文件解决问题
|
||||
crash.advice.ConcurrentModificationException=这可能是因为您的Java版本高于Java 1.8.0_11导致的,可以尝试卸载Java8安装Java7。
|
||||
crash.advice.LWJGLException=您的电脑不正常,可能需要使用驱动精灵或其他安装器更新显卡驱动。
|
||||
@@ -106,13 +107,12 @@ 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。
|
||||
|
||||
download=下载
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi.bangbang93.com/)
|
||||
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
|
||||
download.failed=下载失败
|
||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||
download.mojang=官方
|
||||
@@ -147,7 +147,7 @@ install.installer.forge=Forge
|
||||
install.installer.game=游戏
|
||||
install.installer.install=安装%s
|
||||
install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=不安装%s
|
||||
install.installer.not_installed=暂不安装%s,可以点击此处安装
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s版本
|
||||
install.modpack=安装整合包
|
||||
@@ -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=通用
|
||||
@@ -311,6 +310,7 @@ profile=配置
|
||||
profile.default=当前目录
|
||||
profile.home=主文件夹
|
||||
profile.instance_directory=游戏路径
|
||||
profile.instance_directory.choose=选择游戏路径
|
||||
profile.new=新建配置
|
||||
profile.new_name=新配置名:
|
||||
profile.remove=真的要删除配置%s吗?
|
||||
@@ -343,7 +343,6 @@ settings.advanced.server_ip=直入服务器ip地址(不必填写,启动游戏
|
||||
settings.advanced.wrapper_launcher=前置指令(不必填写,如optirun)
|
||||
|
||||
settings.cannot_remove_default_config=不能删除默认配置
|
||||
settings.choose_gamedir=选择游戏路径
|
||||
settings.custom=自定义
|
||||
settings.default=默认
|
||||
settings.failed_load=设置文件加载失败,可能是升级了启动器或被人工修改造成错误,是否清除?
|
||||
@@ -352,6 +351,8 @@ settings.game=游戏设置
|
||||
settings.game.dimension=游戏窗口分辨率
|
||||
settings.game.exploration=浏览
|
||||
settings.game.fullscreen=全屏
|
||||
settings.game.game_directory=游戏路径(版本隔离)
|
||||
settings.game.game_directory.choose=选择游戏路径
|
||||
settings.game.java_directory=Java路径
|
||||
settings.game.java_directory.choose=选择Java路径
|
||||
settings.game.management=管理
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -27,41 +30,49 @@ 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();
|
||||
|
||||
public abstract void clearCache();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("username", getUsername())
|
||||
.append("character", getCharacter())
|
||||
.append("uuid", getUUID())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class AccountBuilder<T extends Account> {
|
||||
private CharacterSelector selector = CharacterSelector.DEFAULT;
|
||||
private String username;
|
||||
private String password = null;
|
||||
private Object additionalData = null;
|
||||
private Proxy proxy = Proxy.NO_PROXY;
|
||||
|
||||
public AccountBuilder() {
|
||||
}
|
||||
|
||||
public AccountBuilder setSelector(CharacterSelector selector) {
|
||||
this.selector = Objects.requireNonNull(selector);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountBuilder setUsername(String username) {
|
||||
this.username = Objects.requireNonNull(username);
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountBuilder setPassword(String password) {
|
||||
this.password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountBuilder setAdditionalData(Object additionalData) {
|
||||
this.additionalData = additionalData;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AccountBuilder setProxy(Proxy proxy) {
|
||||
this.proxy = Objects.requireNonNull(proxy);
|
||||
return this;
|
||||
}
|
||||
|
||||
public T create(AccountFactory<T> factory) throws AuthenticationException {
|
||||
return factory.create(selector, Objects.requireNonNull(username), password, additionalData, proxy);
|
||||
}
|
||||
}
|
||||
@@ -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 <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public enum UserType {
|
||||
LEGACY,
|
||||
MOJANG;
|
||||
|
||||
public static UserType fromName(String name) {
|
||||
return BY_NAME.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
public static UserType fromLegacy(boolean isLegacy) {
|
||||
return isLegacy ? LEGACY : MOJANG;
|
||||
}
|
||||
|
||||
static {
|
||||
HashMap<String, UserType> byName = new HashMap<>();
|
||||
for (UserType type : values())
|
||||
byName.put(type.name().toLowerCase(), type);
|
||||
BY_NAME = Collections.unmodifiableMap(byName);
|
||||
}
|
||||
|
||||
public static final Map<String, UserType> BY_NAME;
|
||||
|
||||
}
|
||||
+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,15 +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.Lang;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
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
|
||||
@@ -36,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);
|
||||
|
||||
@@ -51,7 +54,7 @@ public class OfflineAccount extends Account {
|
||||
|
||||
@Override
|
||||
public UUID getUUID() {
|
||||
return UUIDTypeAdapter.fromString(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,10 +69,10 @@ public class OfflineAccount extends Account {
|
||||
|
||||
@Override
|
||||
public AuthInfo logIn() throws AuthenticationException {
|
||||
if (StringUtils.isBlank(username) || StringUtils.isBlank(uuid))
|
||||
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
|
||||
@@ -78,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)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,6 +100,9 @@ public class OfflineAccount extends Account {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OfflineAccount[username=" + username + ", uuid=" + uuid + "]";
|
||||
return new ToStringBuilder(this)
|
||||
.append("username", username)
|
||||
.append("uuid", uuid)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -19,11 +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.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -42,16 +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));
|
||||
|
||||
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 <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.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 <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.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();
|
||||
}
|
||||
@@ -19,7 +19,7 @@ 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.LiteLoaderVersionList;
|
||||
import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
|
||||
|
||||
/**
|
||||
@@ -33,29 +33,14 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
private BMCLAPIDownloadProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLibraryBaseURL() {
|
||||
return "http://bmclapi2.bangbang93.com/libraries/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionListURL() {
|
||||
return "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersionBaseURL() {
|
||||
return "http://bmclapi2.bangbang93.com/versions/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetIndexBaseURL() {
|
||||
return "http://bmclapi2.bangbang93.com/indexes/";
|
||||
return "https://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetBaseURL() {
|
||||
return "http://bmclapi2.bangbang93.com/assets/";
|
||||
return "https://bmclapi2.bangbang93.com/assets/";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,7 +51,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
case "forge":
|
||||
return ForgeVersionList.INSTANCE;
|
||||
case "liteloader":
|
||||
return LiteLoaderVersionList.INSTANCE;
|
||||
return LiteLoaderBMCLVersionList.INSTANCE;
|
||||
case "optifine":
|
||||
return OptiFineBMCLVersionList.INSTANCE;
|
||||
default:
|
||||
@@ -80,7 +65,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
.replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
|
||||
.replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com")
|
||||
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
|
||||
.replace("http://files.minecraftforge.net/maven", "https://bmclapi2.bangbang93.com/maven")
|
||||
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven")
|
||||
.replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
|
||||
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven");
|
||||
}
|
||||
|
||||
@@ -20,12 +20,8 @@ package org.jackhuang.hmcl.download;
|
||||
public class CurseCDNDownloadProvider extends MojangDownloadProvider {
|
||||
public static final CurseCDNDownloadProvider INSTANCE = new CurseCDNDownloadProvider();
|
||||
|
||||
protected CurseCDNDownloadProvider() {
|
||||
super(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String injectURL(String baseURL) {
|
||||
return baseURL == null ? null : baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
|
||||
return baseURL == null ? null : baseURL.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://ftb.cursecdn.com/FTB2/maven");
|
||||
}
|
||||
}
|
||||
@@ -24,14 +24,8 @@ package org.jackhuang.hmcl.download;
|
||||
*/
|
||||
public interface DownloadProvider {
|
||||
|
||||
String getLibraryBaseURL();
|
||||
|
||||
String getVersionListURL();
|
||||
|
||||
String getVersionBaseURL();
|
||||
|
||||
String getAssetIndexBaseURL();
|
||||
|
||||
String getAssetBaseURL();
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,9 +41,12 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
Version newVersion = version;
|
||||
setResult(maintain(version));
|
||||
}
|
||||
|
||||
public static Version maintain(Version version) {
|
||||
Library forge = null, liteLoader = null, optiFine = null;
|
||||
List<String> args = new ArrayList<>(StringUtils.tokenize(newVersion.getMinecraftArguments().orElse("")));
|
||||
List<String> args = new ArrayList<>(StringUtils.tokenize(version.getMinecraftArguments().orElse("")));
|
||||
|
||||
for (Library library : version.getLibraries()) {
|
||||
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge"))
|
||||
@@ -84,11 +87,13 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
}
|
||||
|
||||
if ((liteLoader != null || forge != null) && optiFine != null) {
|
||||
// If forge or LiteLoader installed, OptiFine Tweaker will cause crash.
|
||||
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
|
||||
removeTweakClass(args, "optifine");
|
||||
args.add("--tweakClass");
|
||||
args.add("optifine.OptiFineForgeTweaker");
|
||||
}
|
||||
|
||||
setResult(newVersion.setMinecraftArguments(StringUtils.makeCommand(args)));
|
||||
return version.setMinecraftArguments(StringUtils.makeCommand(args));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,35 +28,14 @@ import org.jackhuang.hmcl.download.optifine.OptiFineVersionList;
|
||||
*/
|
||||
public class MojangDownloadProvider implements DownloadProvider {
|
||||
|
||||
private final boolean isChina;
|
||||
|
||||
public MojangDownloadProvider(boolean isChina) {
|
||||
this.isChina = isChina;
|
||||
}
|
||||
|
||||
@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 "http://s3.amazonaws.com/Minecraft.Download/versions/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetIndexBaseURL() {
|
||||
return "http://s3.amazonaws.com/Minecraft.Download/indexes/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAssetBaseURL() {
|
||||
return "http://resources.download.minecraft.net/";
|
||||
return "https://resources.download.minecraft.net/";
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,16 +56,6 @@ public class MojangDownloadProvider implements DownloadProvider {
|
||||
|
||||
@Override
|
||||
public String injectURL(String baseURL) {
|
||||
if (baseURL == null)
|
||||
return null;
|
||||
//else if (baseURL.contains("scala-swing") || baseURL.contains("scala-xml") || baseURL.contains("scala-parser-combinators"))
|
||||
// return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
|
||||
/*else if (baseURL.contains("typesafe") || baseURL.contains("scala"))
|
||||
if (isChina)
|
||||
return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
|
||||
else
|
||||
return baseURL.replace("http://files.minecraftforge.net/maven", "http://repo1.maven.org/maven2");
|
||||
*/else
|
||||
return baseURL;
|
||||
}
|
||||
}
|
||||
@@ -92,5 +92,5 @@ public final class ForgeVersionList extends VersionList<Void> {
|
||||
};
|
||||
}
|
||||
|
||||
public static final String FORGE_LIST = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/json";
|
||||
public static final String FORGE_LIST = "https://files.minecraftforge.net/maven/net/minecraftforge/forge/json";
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.game.CompatibilityRule;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.TaskResult;
|
||||
@@ -24,7 +25,10 @@ import org.jackhuang.hmcl.util.Constants;
|
||||
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> {
|
||||
@@ -44,7 +48,6 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
public void execute() {
|
||||
List<Library> libraries = new ArrayList<>(version.getLibraries());
|
||||
|
||||
Map<String, VersionNumber> versionMap = new HashMap<>();
|
||||
SimpleMultimap<String, Library> multimap = new SimpleMultimap<String, Library>(HashMap::new, LinkedList::new);
|
||||
|
||||
for (Library library : libraries) {
|
||||
@@ -52,32 +55,36 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
|
||||
VersionNumber number = VersionNumber.asVersion(library.getVersion());
|
||||
String serialized = Constants.GSON.toJson(library);
|
||||
|
||||
if (versionMap.containsKey(id)) {
|
||||
VersionNumber otherNumber = versionMap.get(id);
|
||||
if (number.compareTo(otherNumber) > 0) {
|
||||
multimap.removeKey(id);
|
||||
versionMap.put(id, number);
|
||||
multimap.put(id, library);
|
||||
} else if (number.compareTo(otherNumber) == 0) { // same library id.
|
||||
boolean flag = false;
|
||||
// prevent from duplicated libraries
|
||||
for (Library otherLibrary : multimap.get(id))
|
||||
if (library.equals(otherLibrary)) {
|
||||
String otherSerialized = Constants.GSON.toJson(otherLibrary);
|
||||
// A trick, the library that has more information is better, which can be
|
||||
// considered whose serialized JSON text will be longer.
|
||||
if (serialized.length() <= otherSerialized.length()) {
|
||||
flag = true;
|
||||
break;
|
||||
} else {
|
||||
multimap.removeValue(id, otherLibrary);
|
||||
if (multimap.containsKey(id)) {
|
||||
boolean hasEqualRule = false;
|
||||
for (Library otherLibrary : multimap.get(id)) {
|
||||
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
|
||||
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
|
||||
hasEqualRule = true;
|
||||
if (number.compareTo(otherNumber) > 0) { // if this library is newer
|
||||
multimap.removeValue(otherLibrary);
|
||||
multimap.put(id, library);
|
||||
break;
|
||||
} else if (number.compareTo(otherNumber) == 0) { // same library id.
|
||||
// prevent from duplicated libraries
|
||||
if (library.equals(otherLibrary)) {
|
||||
String otherSerialized = Constants.GSON.toJson(otherLibrary);
|
||||
// A trick, the library that has more information is better, which can be
|
||||
// considered whose serialized JSON text will be longer.
|
||||
if (serialized.length() > otherSerialized.length()) {
|
||||
multimap.removeValue(id, otherLibrary);
|
||||
multimap.put(id, library);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!flag)
|
||||
multimap.put(id, library);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasEqualRule) {
|
||||
multimap.put(id, library);
|
||||
}
|
||||
} else {
|
||||
versionMap.put(id, number);
|
||||
multimap.put(id, library);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.liteloader;
|
||||
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.VersionList;
|
||||
import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.VersionNumber;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersionTag> {
|
||||
|
||||
public static final LiteLoaderBMCLVersionList INSTANCE = new LiteLoaderBMCLVersionList();
|
||||
|
||||
private LiteLoaderBMCLVersionList() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
|
||||
return new Task() {
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
lock.writeLock().lock();
|
||||
|
||||
try {
|
||||
LiteLoaderVersionsRoot root = Constants.GSON.fromJson(task.getResult(), LiteLoaderVersionsRoot.class);
|
||||
versions.clear();
|
||||
|
||||
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
|
||||
String gameVersion = entry.getKey();
|
||||
LiteLoaderGameVersions liteLoader = entry.getValue();
|
||||
Optional<String> gg = VersionNumber.parseVersion(gameVersion);
|
||||
if (!gg.isPresent())
|
||||
continue;
|
||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
|
||||
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
|
||||
}
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
|
||||
if (branch == null || repository == null)
|
||||
return;
|
||||
|
||||
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
|
||||
String branchName = entry.getKey();
|
||||
LiteLoaderVersion v = entry.getValue();
|
||||
if ("latest".equals(branchName))
|
||||
continue;
|
||||
|
||||
String version = v.getVersion();
|
||||
String url = "http://bmclapi2.bangbang93.com/liteloader/download?version=" + version;
|
||||
if (snapshot) {
|
||||
try {
|
||||
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
|
||||
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
|
||||
version, downloadProvider.injectURL(url),
|
||||
v.getTweakClass(), v.getLibraries()
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";
|
||||
|
||||
private static String getLatestSnapshotVersion(String repo) throws Exception {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document doc = builder.parse(repo + "maven-metadata.xml");
|
||||
Element r = doc.getDocumentElement();
|
||||
Element snapshot = (Element) r.getElementsByTagName("snapshot").item(0);
|
||||
Node timestamp = snapshot.getElementsByTagName("timestamp").item(0);
|
||||
Node buildNumber = snapshot.getElementsByTagName("buildNumber").item(0);
|
||||
return timestamp.getTextContent() + "-" + buildNumber.getTextContent();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -42,7 +42,7 @@ public final class OptiFineBMCLVersionList extends VersionList<Void> {
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi.bangbang93.com/optifine/versionlist"));
|
||||
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi2.bangbang93.com/optifine/versionlist"));
|
||||
return new Task() {
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
|
||||
+4
-2
@@ -70,13 +70,13 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
String remoteVersion = remote.getSelfVersion();
|
||||
String remoteVersion = remote.getGameVersion() + "-" + remote.getSelfVersion();
|
||||
|
||||
Library library = new Library(
|
||||
"net.optifine", "optifine", remoteVersion, null, null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo(
|
||||
"net/optifine/optifine/" + remoteVersion + "/optifine-" + remoteVersion + ".jar",
|
||||
remote.getUrl())), true
|
||||
remote.getUrl()))
|
||||
);
|
||||
|
||||
List<Library> libraries = new LinkedList<>();
|
||||
@@ -99,6 +99,8 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
|
||||
String minecraftArguments = version.getMinecraftArguments().orElse("");
|
||||
if (!hasFMLTweaker)
|
||||
minecraftArguments = minecraftArguments + " --tweakClass optifine.OptiFineTweaker";
|
||||
else
|
||||
minecraftArguments = minecraftArguments + " --tweakClass optifine.OptiFineForgeTweaker";
|
||||
|
||||
if (version.getMainClass() == null || !version.getMainClass().startsWith("net.minecraft.launchwrapper."))
|
||||
libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12"));
|
||||
|
||||
+2
-2
@@ -52,7 +52,7 @@ public final class OptiFineVersionList extends VersionList<Void> {
|
||||
|
||||
@Override
|
||||
public Task refreshAsync(DownloadProvider downloadProvider) {
|
||||
GetTask task = new GetTask(NetworkUtils.toURL("http://optifine.net/downloads"));
|
||||
GetTask task = new GetTask(NetworkUtils.toURL("https://optifine.net/downloads"));
|
||||
return new Task() {
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
@@ -116,6 +116,6 @@ public final class OptiFineVersionList extends VersionList<Void> {
|
||||
result = m.group(1);
|
||||
if (result == null)
|
||||
throw new IllegalStateException("Cannot find version in " + content);
|
||||
return "http://optifine.net/downloadx?f=OptiFine" + result;
|
||||
return "https://optifine.net/downloadx?f=OptiFine" + result;
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ClassicVersion extends Version {
|
||||
public ClassicLibrary(String name) {
|
||||
super("", "", "", null, null,
|
||||
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
|
||||
false, null, null, null, null);
|
||||
null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,25 @@ public final class CompatibilityRule {
|
||||
return action == Action.ALLOW;
|
||||
}
|
||||
|
||||
public static boolean equals(Collection<CompatibilityRule> rules1, Collection<CompatibilityRule> rules2) {
|
||||
return Objects.hashCode(rules1) == Objects.hashCode(rules2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
CompatibilityRule that = (CompatibilityRule) o;
|
||||
return action == that.action &&
|
||||
Objects.equals(os, that.os) &&
|
||||
Objects.equals(features, that.features);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(action, os, features);
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
ALLOW,
|
||||
DISALLOW
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -116,7 +117,7 @@ public final class GameVersion {
|
||||
.getEntry("net/minecraft/client/Minecraft.class");
|
||||
if (minecraft != null)
|
||||
return getVersionOfOldMinecraft(f, minecraft);
|
||||
ZipArchiveEntry main = f.getEntry("net/minecraft/client/main/Launcher.class");
|
||||
ZipArchiveEntry main = f.getEntry("net/minecraft/client/main/Main.class");
|
||||
ZipArchiveEntry minecraftServer = f.getEntry("net/minecraft/server/MinecraftServer.class");
|
||||
if ((main != null) && (minecraftServer != null))
|
||||
return getVersionOfNewMinecraft(f, minecraftServer);
|
||||
|
||||
@@ -45,7 +45,6 @@ public class Library implements Comparable<Library> {
|
||||
private final LibrariesDownloadInfo downloads;
|
||||
private final LibraryDownloadInfo download;
|
||||
private final ExtractRules extract;
|
||||
private final boolean lateload;
|
||||
private final Map<OperatingSystem, String> natives;
|
||||
private final List<CompatibilityRule> rules;
|
||||
private final List<String> checksums;
|
||||
@@ -55,16 +54,12 @@ public class Library implements Comparable<Library> {
|
||||
public Library(String groupId, String artifactId, String version) {
|
||||
this(groupId, artifactId, version, null, null, null);
|
||||
}
|
||||
|
||||
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads) {
|
||||
this(groupId, artifactId, version, classifier, url, downloads, false);
|
||||
this(groupId, artifactId, version, classifier, url, downloads, null, null, null, null);
|
||||
}
|
||||
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload) {
|
||||
this(groupId, artifactId, version, classifier, url, downloads, lateload, null, null, null, null);
|
||||
}
|
||||
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||
this.groupId = groupId;
|
||||
this.artifactId = artifactId;
|
||||
this.version = version;
|
||||
@@ -78,7 +73,6 @@ public class Library implements Comparable<Library> {
|
||||
this.url = url;
|
||||
this.downloads = downloads;
|
||||
this.extract = extract;
|
||||
this.lateload = lateload;
|
||||
this.natives = natives;
|
||||
this.rules = rules;
|
||||
this.checksums = checksums;
|
||||
@@ -139,14 +133,14 @@ public class Library implements Comparable<Library> {
|
||||
return download;
|
||||
}
|
||||
|
||||
public boolean isLateload() {
|
||||
return lateload;
|
||||
}
|
||||
|
||||
public List<String> getChecksums() {
|
||||
return checksums;
|
||||
}
|
||||
|
||||
public List<CompatibilityRule> getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
||||
public boolean is(String groupId, String artifactId) {
|
||||
return this.groupId.equals(groupId) && this.artifactId.equals(artifactId);
|
||||
}
|
||||
@@ -179,7 +173,7 @@ public class Library implements Comparable<Library> {
|
||||
}
|
||||
|
||||
public Library setClassifier(String classifier) {
|
||||
return new Library(groupId, artifactId, version, classifier, url, downloads, lateload, checksums, extract, natives, rules);
|
||||
return new Library(groupId, artifactId, version, classifier, url, downloads, checksums, extract, natives, rules);
|
||||
}
|
||||
|
||||
public static Library fromName(String name) {
|
||||
@@ -191,7 +185,7 @@ public class Library implements Comparable<Library> {
|
||||
if (arr.length != 3 && arr.length != 4)
|
||||
throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier).");
|
||||
|
||||
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, false, checksums, extract, natives, rules);
|
||||
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, checksums, extract, natives, rules);
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonDeserializer<Library>, JsonSerializer<Library> {
|
||||
|
||||
@@ -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
|
||||
@@ -110,29 +113,23 @@ public class DefaultLauncher extends Launcher {
|
||||
res.add("-Dfml.ignorePatchDiscrepancies=true");
|
||||
}
|
||||
|
||||
LinkedList<File> lateload = new LinkedList<>();
|
||||
StringBuilder classpath = new StringBuilder();
|
||||
LinkedList<String> classpath = new LinkedList<>();
|
||||
for (Library library : version.getLibraries())
|
||||
if (library.appliesToCurrentEnvironment() && !library.isNative()) {
|
||||
File f = repository.getLibraryFile(version, library);
|
||||
if (f.exists() && f.isFile())
|
||||
if (library.isLateload())
|
||||
lateload.add(f);
|
||||
else
|
||||
classpath.append(f.getAbsolutePath()).append(OperatingSystem.PATH_SEPARATOR);
|
||||
classpath.add(f.getAbsolutePath());
|
||||
}
|
||||
for (File library : lateload)
|
||||
classpath.append(library.getAbsolutePath()).append(OperatingSystem.PATH_SEPARATOR);
|
||||
|
||||
File jar = repository.getVersionJar(version);
|
||||
if (!jar.exists() || !jar.isFile())
|
||||
throw new IOException("Minecraft jar does not exist");
|
||||
classpath.append(jar.getAbsolutePath());
|
||||
classpath.add(jar.getAbsolutePath());
|
||||
|
||||
// Provided Minecraft arguments
|
||||
File gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId());
|
||||
Map<String, String> configuration = getConfigurations();
|
||||
configuration.put("${classpath}", classpath.toString());
|
||||
configuration.put("${classpath}", String.join(OperatingSystem.PATH_SEPARATOR, classpath));
|
||||
configuration.put("${natives_directory}", nativeFolder.getAbsolutePath());
|
||||
configuration.put("${game_assets}", gameAssets.getAbsolutePath());
|
||||
configuration.put("${assets_root}", gameAssets.getAbsolutePath());
|
||||
@@ -197,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() {
|
||||
@@ -238,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())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -276,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;
|
||||
}
|
||||
@@ -301,7 +296,7 @@ public class DefaultLauncher extends Launcher {
|
||||
if (isWindows) {
|
||||
writer.write("@echo off");
|
||||
writer.newLine();
|
||||
writer.write("set APPDATA=" + options.getGameDir().getParent());
|
||||
writer.write("set APPDATA=" + options.getGameDir().getAbsoluteFile().getParent());
|
||||
writer.newLine();
|
||||
writer.write("cd /D %APPDATA%");
|
||||
writer.newLine();
|
||||
@@ -316,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user