Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
129f6bb5cc | ||
|
|
545c17d052 | ||
|
|
add00ddba1 | ||
|
|
d9aeb0cc2b | ||
|
|
49e70e7195 | ||
|
|
f8abebf110 | ||
|
|
8e2dfc7341 | ||
|
|
e650ffffce | ||
|
|
d549a300ab | ||
|
|
82a62e73c1 | ||
|
|
45c7e0c231 | ||
|
|
b98a4b11a3 | ||
|
|
484a103f27 | ||
|
|
e31120be21 | ||
|
|
247f02d423 | ||
|
|
3da0c1d8e3 | ||
|
|
c830bbc703 | ||
|
|
7b619fef1d | ||
|
|
bac265a077 | ||
|
|
39df0f8d43 | ||
|
|
6af7f916ee | ||
|
|
ae64097edf | ||
|
|
cb6d8a88f1 | ||
|
|
12838994ad | ||
|
|
d30758a2a8 | ||
|
|
b820aad358 | ||
|
|
170189c344 | ||
|
|
f9e9c9d38b | ||
|
|
088af981df | ||
|
|
357fef8672 | ||
|
|
c38848ea0e | ||
|
|
be8cd13b08 | ||
|
|
487816ab74 | ||
|
|
bcd9f8240c | ||
|
|
1b62972f06 | ||
|
|
1117c3bfab | ||
|
|
748d195db5 | ||
|
|
3a9017adb8 | ||
|
|
eff3186ab5 | ||
|
|
e5f5fe878e | ||
|
|
a47f0f6ecf | ||
|
|
9896165b68 | ||
|
|
5e3adcee93 | ||
|
|
b252266a46 | ||
|
|
1d5adea114 | ||
|
|
a64a51c7df | ||
|
|
ec41c13b45 | ||
|
|
486ca434ff | ||
|
|
fe87a899c8 | ||
|
|
06806bd383 | ||
|
|
2f6c7e1ce2 | ||
|
|
141cda13f1 | ||
|
|
3e8c8afcd1 | ||
|
|
8236f613e0 | ||
|
|
ea6108dca7 | ||
|
|
985a4f07e0 | ||
|
|
5642c579a4 | ||
|
|
8e0559fb3b | ||
|
|
a6a1755fee | ||
|
|
846f383f7b | ||
|
|
bbbe3726f2 | ||
|
|
6d83fd9903 | ||
|
|
fac62a8d24 | ||
|
|
b0d71a5511 | ||
|
|
124ba34d48 | ||
|
|
75270b8ad6 | ||
|
|
5cf6ef88a6 |
No files matched your search
Binary file not shown.
+25
-67
@@ -1,5 +1,4 @@
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
import proguard.gradle.ProGuardTask
|
||||
|
||||
import java.security.MessageDigest
|
||||
import java.util.jar.JarFile
|
||||
@@ -54,11 +53,6 @@ jar {
|
||||
}
|
||||
|
||||
doLast {
|
||||
//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 ->
|
||||
messageDigest.update(buf, 0, bytesRead)
|
||||
@@ -76,6 +70,29 @@ processResources {
|
||||
}
|
||||
}
|
||||
|
||||
task makePackGZ(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
makeExecutableinjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
makeExecutableoutjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
}
|
||||
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz")
|
||||
def os = new GZIPOutputStream(new FileOutputStream(loc))
|
||||
Pack200.newPacker().pack new JarFile(makeExecutableinjar), os
|
||||
os.close()
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
loc.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
messageDigest.update(buf, 0, bytesRead)
|
||||
}
|
||||
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
|
||||
def fileEx = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz.sha1")
|
||||
if (!fileEx.exists()) fileEx.createNewFile()
|
||||
fileEx.append sha1Hex
|
||||
}
|
||||
|
||||
task makeExecutable(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
@@ -100,64 +117,5 @@ task makeExecutable(dependsOn: jar) doLast {
|
||||
|
||||
}
|
||||
|
||||
task makePackGZ(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
makeExecutableinjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
makeExecutableoutjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
}
|
||||
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz")
|
||||
def os = new GZIPOutputStream(new FileOutputStream(loc))
|
||||
Pack200.newPacker().pack new JarFile(makeExecutableinjar), os
|
||||
os.close()
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
loc.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
messageDigest.update(buf, 0, bytesRead)
|
||||
}
|
||||
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
|
||||
def fileEx = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz.sha1")
|
||||
if (!fileEx.exists()) fileEx.createNewFile()
|
||||
fileEx.append sha1Hex
|
||||
}
|
||||
|
||||
task proguard(type: ProGuardTask, dependsOn: jar) {
|
||||
ext {
|
||||
def re = jar.classifier
|
||||
injar = jar.archivePath
|
||||
jar.classifier = 'optimized'
|
||||
outjar = jar.archivePath
|
||||
jar.classifier = re
|
||||
}
|
||||
|
||||
injars(/*["filter": "!META-INF/**,**"], */injar)
|
||||
String javaHome = System.getProperty("java.home")
|
||||
if (!javaHome.endsWith("jre"))
|
||||
javaHome += File.separator + "jre"
|
||||
libraryjars javaHome + "/lib/rt.jar"
|
||||
libraryjars javaHome + "/lib/jce.jar"
|
||||
libraryjars javaHome + "/lib/jsse.jar"
|
||||
libraryjars javaHome + "/lib/ext/jfxrt.jar"
|
||||
outjars outjar
|
||||
|
||||
configuration 'proguard.pro'
|
||||
}
|
||||
|
||||
task combineMETA_INF(type: Jar) {
|
||||
ext {
|
||||
def re = jar.classifier
|
||||
injar = jar.archivePath
|
||||
jar.classifier = 'optimized'
|
||||
outjar = jar.archivePath
|
||||
jar.classifier = re
|
||||
}
|
||||
from zipTree(outjar)
|
||||
from zipTree(injar).matching {
|
||||
include "META-INF/"
|
||||
}
|
||||
}
|
||||
|
||||
build.dependsOn makeExecutable
|
||||
build.dependsOn makePackGZ
|
||||
build.dependsOn makePackGZ
|
||||
build.dependsOn makeExecutable
|
||||
Vendored
-15
@@ -1,15 +0,0 @@
|
||||
-target 1.8
|
||||
-dontoptimize
|
||||
-dontobfuscate
|
||||
|
||||
# JFoenix
|
||||
-keep class com.jfoenix.** {
|
||||
<fields>;
|
||||
<methods>;
|
||||
}
|
||||
|
||||
# HMCL
|
||||
-keep class org.jackhuang.** {
|
||||
<fields>;
|
||||
<methods>;
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
@@ -34,6 +36,7 @@ import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -79,11 +82,13 @@ public final class Launcher extends Application {
|
||||
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
|
||||
UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args));
|
||||
|
||||
Logging.LOG.info("*** " + TITLE + " ***");
|
||||
Logging.LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||
Logging.LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
Logging.LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
Logging.LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
LOG.info("*** " + TITLE + " ***");
|
||||
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
LOG.info("Current Directory: " + Paths.get("").toAbsolutePath());
|
||||
LOG.info("HMCL Directory: " + HMCL_DIRECTORY);
|
||||
|
||||
launch(args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
@@ -92,7 +97,7 @@ public final class Launcher extends Application {
|
||||
}
|
||||
|
||||
public static void stopApplication() {
|
||||
Logging.LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
@@ -106,7 +111,7 @@ public final class Launcher extends Application {
|
||||
}
|
||||
|
||||
public static void stopWithoutPlatform() {
|
||||
Logging.LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
|
||||
@@ -21,6 +21,8 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
@@ -32,12 +34,17 @@ import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
|
||||
public final class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
checkDSTRootCAX3();
|
||||
checkConfigPermission();
|
||||
|
||||
ConfigHolder.init();
|
||||
Launcher.main(args);
|
||||
}
|
||||
|
||||
@@ -80,6 +87,22 @@ public final class Main {
|
||||
showWarningAndContinue(i18n("fatal.missing_dst_root_ca_x3"));
|
||||
}
|
||||
|
||||
private static void checkConfigPermission() {
|
||||
Path config = ConfigHolder.CONFIG_PATH;
|
||||
if (Files.exists(config)) {
|
||||
if (Files.isReadable(config) && Files.isWritable(config)) {
|
||||
// we are able to read & write the existent config
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (Files.isWritable(config.getParent())) {
|
||||
// we are able to create a new config
|
||||
return;
|
||||
}
|
||||
}
|
||||
showErrorAndExit(i18n("fatal.config_access_denied", config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.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>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class AccountLoadingEvent extends Event {
|
||||
|
||||
private final Collection<Account> accounts;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Scheduler;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -33,7 +33,6 @@ import org.jackhuang.hmcl.ui.DialogController;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.Proxy;
|
||||
import java.util.*;
|
||||
|
||||
public final class AccountHelper {
|
||||
@@ -43,31 +42,19 @@ public final class AccountHelper {
|
||||
public static final File SKIN_DIR = new File(Launcher.HMCL_DIRECTORY, "skins");
|
||||
|
||||
public static void loadSkins() {
|
||||
loadSkins(Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public static void loadSkins(Proxy proxy) {
|
||||
for (Account account : Settings.INSTANCE.getAccounts()) {
|
||||
for (Account account : Accounts.getAccounts()) {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
new SkinLoadTask((YggdrasilAccount) account, proxy, false).start();
|
||||
new SkinLoadTask((YggdrasilAccount) account, false).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Task loadSkinAsync(YggdrasilAccount account) {
|
||||
return loadSkinAsync(account, Settings.INSTANCE.getProxy());
|
||||
}
|
||||
|
||||
public static Task loadSkinAsync(YggdrasilAccount account, Proxy proxy) {
|
||||
return new SkinLoadTask(account, proxy, false);
|
||||
return new SkinLoadTask(account, false);
|
||||
}
|
||||
|
||||
public static Task refreshSkinAsync(YggdrasilAccount account) {
|
||||
return refreshSkinAsync(account, Settings.INSTANCE.getProxy());
|
||||
}
|
||||
|
||||
public static Task refreshSkinAsync(YggdrasilAccount account, Proxy proxy) {
|
||||
return new SkinLoadTask(account, proxy, true);
|
||||
return new SkinLoadTask(account, true);
|
||||
}
|
||||
|
||||
private static File getSkinFile(UUID uuid) {
|
||||
@@ -94,9 +81,9 @@ public final class AccountHelper {
|
||||
return getDefaultSkin(uuid, scaleRatio);
|
||||
}
|
||||
|
||||
public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio, Proxy proxy) throws Exception {
|
||||
public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio) throws Exception {
|
||||
File file = getSkinFile(profile.getId());
|
||||
downloadSkin(account, profile, true, proxy);
|
||||
downloadSkin(account, profile, true);
|
||||
if (!file.exists())
|
||||
return getDefaultSkin(profile.getId(), scaleRatio);
|
||||
|
||||
@@ -111,17 +98,15 @@ public final class AccountHelper {
|
||||
|
||||
private static class SkinLoadTask extends Task {
|
||||
private final YggdrasilAccount account;
|
||||
private final Proxy proxy;
|
||||
private final boolean refresh;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
public SkinLoadTask(YggdrasilAccount account, Proxy proxy) {
|
||||
this(account, proxy, false);
|
||||
public SkinLoadTask(YggdrasilAccount account) {
|
||||
this(account, false);
|
||||
}
|
||||
|
||||
public SkinLoadTask(YggdrasilAccount account, Proxy proxy, boolean refresh) {
|
||||
public SkinLoadTask(YggdrasilAccount account, boolean refresh) {
|
||||
this.account = account;
|
||||
this.proxy = proxy;
|
||||
this.refresh = refresh;
|
||||
}
|
||||
|
||||
@@ -140,11 +125,11 @@ public final class AccountHelper {
|
||||
if (!account.isLoggedIn() && (account.getCharacter() == null || refresh))
|
||||
DialogController.logIn(account);
|
||||
|
||||
downloadSkin(account, refresh, proxy);
|
||||
downloadSkin(account, refresh);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh, Proxy proxy) throws Exception {
|
||||
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh) throws Exception {
|
||||
account.clearCache();
|
||||
|
||||
Optional<Texture> texture = account.getSkin(profile);
|
||||
@@ -153,10 +138,10 @@ public final class AccountHelper {
|
||||
File file = getSkinFile(profile.getId());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
private static void downloadSkin(YggdrasilAccount account, boolean refresh, Proxy proxy) throws Exception {
|
||||
private static void downloadSkin(YggdrasilAccount account, boolean refresh) throws Exception {
|
||||
account.clearCache();
|
||||
|
||||
if (account.getCharacter() == null) return;
|
||||
@@ -166,7 +151,7 @@ public final class AccountHelper {
|
||||
File file = getSkinFile(account.getUUID());
|
||||
if (!refresh && file.exists())
|
||||
return;
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
|
||||
new FileDownloadTask(NetworkUtils.toURL(url), file).run();
|
||||
}
|
||||
|
||||
public static Image scale(String url, double scaleRatio) {
|
||||
|
||||
@@ -22,8 +22,6 @@ import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.GameBuilder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -31,11 +29,7 @@ public class HMCLDependencyManager extends DefaultDependencyManager {
|
||||
private final Profile profile;
|
||||
|
||||
public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider) {
|
||||
this(profile, downloadProvider, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider, Proxy proxy) {
|
||||
super(profile.getRepository(), downloadProvider, proxy);
|
||||
super(profile.getRepository(), downloadProvider);
|
||||
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,11 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@@ -58,7 +61,11 @@ public class HMCLGameDownloadTask extends Task {
|
||||
public void execute() {
|
||||
File jar = profile.getRepository().getVersionJar(version);
|
||||
|
||||
File cache = new File(Settings.INSTANCE.getCommonPath(), "jars/" + gameVersion + ".jar");
|
||||
// Force using common directory will not affect the behaviour that repository acts
|
||||
// Since we always copy the downloaded jar to .minecraft/versions/<version>/
|
||||
File cache = new File(Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(Settings.getDefaultCommonDirectory()),
|
||||
"jars/" + gameVersion + ".jar");
|
||||
if (cache.exists())
|
||||
try {
|
||||
FileUtils.copyFile(cache, jar);
|
||||
@@ -70,7 +77,6 @@ public class HMCLGameDownloadTask extends Task {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||
cache,
|
||||
profile.getDependency().getProxy(),
|
||||
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||
).then(Task.of(v -> FileUtils.copyFile(cache, jar))));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Profile profile;
|
||||
private final Map<String, VersionSetting> versionSettings = new HashMap<>();
|
||||
@@ -50,17 +52,16 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
return profile;
|
||||
}
|
||||
|
||||
private boolean useSelf(String version, String assetId) {
|
||||
VersionSetting vs = profile.getVersionSetting(version);
|
||||
return new File(getBaseDirectory(), "assets/indexes/" + assetId + ".json").exists() || vs.isNoCommon();
|
||||
private boolean useSelf(String assetId) {
|
||||
return new File(getBaseDirectory(), "assets/indexes/" + assetId + ".json").exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getAssetDirectory(String version, String assetId) {
|
||||
if (useSelf(version, assetId))
|
||||
if (Settings.instance().isCommonDirectoryDisabled() || useSelf(assetId))
|
||||
return super.getAssetDirectory(version, assetId);
|
||||
else
|
||||
return new File(Settings.INSTANCE.getCommonPath(), "assets");
|
||||
return new File(Settings.instance().getCommonDirectory(), "assets");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,10 +83,10 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
public File getLibraryFile(Version version, Library lib) {
|
||||
VersionSetting vs = profile.getVersionSetting(version.getId());
|
||||
File self = super.getLibraryFile(version, lib);
|
||||
if (self.exists() || vs.isNoCommon())
|
||||
if (Settings.instance().isCommonDirectoryDisabled() || self.exists())
|
||||
return self;
|
||||
else
|
||||
return new File(Settings.INSTANCE.getCommonPath(), "libraries/" + lib.getPath());
|
||||
return new File(Settings.instance().getCommonDirectory(), "libraries/" + lib.getPath());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
@@ -190,6 +191,8 @@ public final class LauncherHelper {
|
||||
message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage();
|
||||
} else if (ex instanceof NotDecompressingNativesException) {
|
||||
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
|
||||
} else if (ex instanceof LibraryDownloadException) {
|
||||
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
} else {
|
||||
message = StringUtils.getStackTrace(ex);
|
||||
}
|
||||
@@ -375,7 +378,7 @@ public final class LauncherHelper {
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > Settings.INSTANCE.getLogLines())
|
||||
if (logs.size() > Settings.instance().getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
if (setting.isShowLogs()) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -29,13 +29,27 @@ import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyListProperty;
|
||||
import javafx.beans.property.ReadOnlyListWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
@@ -43,35 +57,162 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
public final class Accounts {
|
||||
private Accounts() {}
|
||||
|
||||
public static final String OFFLINE_ACCOUNT_KEY = "offline";
|
||||
public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil";
|
||||
public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector";
|
||||
public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE;
|
||||
public static final YggdrasilAccountFactory FACTORY_YGGDRASIL = new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE);
|
||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.instance().getDownloadProvider())::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer);
|
||||
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
||||
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(
|
||||
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.INSTANCE.getDownloadProvider())::getArtifactInfo,
|
||||
Accounts::getOrCreateAuthlibInjectorServer))
|
||||
);
|
||||
private static final String TYPE_OFFLINE = "offline";
|
||||
private static final String TYPE_YGGDRASIL_ACCOUNT = "yggdrasil";
|
||||
private static final String TYPE_AUTHLIB_INJECTOR = "authlibInjector";
|
||||
|
||||
public static String getAccountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY;
|
||||
else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY;
|
||||
else if (account instanceof YggdrasilAccount) return YGGDRASIL_ACCOUNT_KEY;
|
||||
else return YGGDRASIL_ACCOUNT_KEY;
|
||||
private static Map<String, AccountFactory<?>> type2factory = mapOf(
|
||||
pair(TYPE_OFFLINE, FACTORY_OFFLINE),
|
||||
pair(TYPE_YGGDRASIL_ACCOUNT, FACTORY_YGGDRASIL),
|
||||
pair(TYPE_AUTHLIB_INJECTOR, FACTORY_AUTHLIB_INJECTOR));
|
||||
|
||||
private static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return TYPE_OFFLINE;
|
||||
else if (account instanceof AuthlibInjectorAccount)
|
||||
return TYPE_AUTHLIB_INJECTOR;
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return TYPE_YGGDRASIL_ACCOUNT;
|
||||
else
|
||||
throw new IllegalArgumentException("Failed to determine account type: " + account);
|
||||
}
|
||||
|
||||
static String getAccountId(Account account) {
|
||||
return getAccountId(account.getUsername(), account.getCharacter());
|
||||
public static AccountFactory<?> getAccountFactory(Account account) {
|
||||
return type2factory.get(accountType(account));
|
||||
}
|
||||
|
||||
static String getAccountId(String username, String character) {
|
||||
return username + ":" + character;
|
||||
private static String accountId(Account account) {
|
||||
return account.getUsername() + ":" + account.getCharacter();
|
||||
}
|
||||
|
||||
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
|
||||
private static ReadOnlyListProperty<Account> accountsWrapper = new ReadOnlyListWrapper<>(accounts);
|
||||
|
||||
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
accounts.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
// this methods first checks whether the current selection is valid
|
||||
// if it's valid, the underlying storage will be updated
|
||||
// otherwise, the first account will be selected as an alternative(or null if accounts is empty)
|
||||
Account selected = get();
|
||||
if (accounts.isEmpty()) {
|
||||
if (selected == null) {
|
||||
// valid
|
||||
} else {
|
||||
// the previously selected account is gone, we can only set it to null here
|
||||
set(null);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (accounts.contains(selected)) {
|
||||
// valid
|
||||
} else {
|
||||
// the previously selected account is gone
|
||||
set(accounts.get(0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// selection is valid, store it
|
||||
if (!initialized)
|
||||
return;
|
||||
config().setSelectedAccount(selected == null ? "" : accountId(selected));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* True if {@link #init()} hasn't been called.
|
||||
*/
|
||||
private static boolean initialized = false;
|
||||
|
||||
static {
|
||||
accounts.addListener(onInvalidating(Accounts::updateAccountStorages));
|
||||
}
|
||||
|
||||
private static void updateAccountStorages() {
|
||||
// don't update the underlying storage before data loading is completed
|
||||
// otherwise it might cause data loss
|
||||
if (!initialized)
|
||||
return;
|
||||
// update storage
|
||||
config().getAccountStorages().setAll(
|
||||
accounts.stream()
|
||||
.map(account -> {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", accountType(account));
|
||||
return storage;
|
||||
})
|
||||
.collect(toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it's ready to load accounts from {@link ConfigHolder#config()}.
|
||||
*/
|
||||
static void init() {
|
||||
if (initialized)
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
// load accounts
|
||||
config().getAccountStorages().forEach(storage -> {
|
||||
AccountFactory<?> factory = type2factory.get(storage.get("type"));
|
||||
if (factory == null) {
|
||||
LOG.warning("Unrecognized account type: " + storage);
|
||||
return;
|
||||
}
|
||||
Account account;
|
||||
try {
|
||||
account = factory.fromStorage(storage);
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Failed to load account: " + storage, e);
|
||||
return;
|
||||
}
|
||||
accounts.add(account);
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
|
||||
config().getAuthlibInjectorServers().addListener(onInvalidating(Accounts::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
// load selected account
|
||||
selectedAccount.set(
|
||||
accounts.stream()
|
||||
.filter(it -> accountId(it).equals(config().getSelectedAccount()))
|
||||
.findFirst()
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
public static ObservableList<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public static ReadOnlyListProperty<Account> accountsProperty() {
|
||||
return accountsWrapper;
|
||||
}
|
||||
|
||||
public static Account getSelectedAccount() {
|
||||
return selectedAccount.get();
|
||||
}
|
||||
|
||||
public static void setSelectedAccount(Account selectedAccount) {
|
||||
Accounts.selectedAccount.set(selectedAccount);
|
||||
}
|
||||
|
||||
public static ObjectProperty<Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
// ==== authlib-injector ====
|
||||
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
|
||||
return ConfigHolder.CONFIG.authlibInjectorServers.stream()
|
||||
return config().getAuthlibInjectorServers().stream()
|
||||
.filter(server -> url.equals(server.getUrl()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
@@ -85,8 +226,38 @@ public final class Accounts {
|
||||
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
|
||||
}
|
||||
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.add(server);
|
||||
config().getAuthlibInjectorServers().add(server);
|
||||
return server;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
|
||||
* This method performs a check and removes the dangling accounts.
|
||||
*/
|
||||
private static void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.map(AuthlibInjectorAccount.class::cast)
|
||||
.filter(it -> !config().getAuthlibInjectorServers().contains(it.getServer()))
|
||||
.collect(toList())
|
||||
.forEach(accounts::remove);
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== Login type name i18n ===
|
||||
private static Map<AccountFactory<?>, String> loginType2name = mapOf(
|
||||
pair(Accounts.FACTORY_OFFLINE, i18n("account.methods.offline")),
|
||||
pair(Accounts.FACTORY_YGGDRASIL, i18n("account.methods.yggdrasil")),
|
||||
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, i18n("account.methods.authlib_injector")));
|
||||
|
||||
public static String getAccountTypeName(AccountFactory<?> factory) {
|
||||
return Optional.ofNullable(loginType2name.get(factory))
|
||||
.orElseThrow(() -> new IllegalArgumentException("No corresponding login type name"));
|
||||
}
|
||||
|
||||
public static String getAccountTypeName(Account account) {
|
||||
return getAccountTypeName(getAccountFactory(account));
|
||||
}
|
||||
// ====
|
||||
}
|
||||
@@ -18,8 +18,11 @@
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.hildan.fxgson.creators.ObservableListCreator;
|
||||
import org.hildan.fxgson.creators.ObservableMapCreator;
|
||||
@@ -27,19 +30,27 @@ import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.util.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -47,7 +58,7 @@ import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
|
||||
public final class Config implements Cloneable {
|
||||
public final class Config implements Cloneable, Observable {
|
||||
|
||||
private static final Gson CONFIG_GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
|
||||
@@ -57,77 +68,122 @@ public final class Config implements Cloneable {
|
||||
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
|
||||
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
|
||||
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
|
||||
.registerTypeAdapter(Theme.class, new Theme.TypeAdapter())
|
||||
.registerTypeAdapter(SupportedLocale.class, new SupportedLocale.TypeAdapter())
|
||||
.registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType
|
||||
.registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
public static Config fromJson(String json) throws JsonParseException {
|
||||
return CONFIG_GSON.fromJson(json, Config.class);
|
||||
Config instance = CONFIG_GSON.fromJson(json, Config.class);
|
||||
// Gson will replace the property fields (even they are final!)
|
||||
// So we have to add the listeners again after deserialization
|
||||
instance.addListenerToProperties();
|
||||
return instance;
|
||||
}
|
||||
|
||||
@SerializedName("last")
|
||||
public final StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
private StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("backgroundType")
|
||||
public final IntegerProperty backgroundImageType = new SimpleIntegerProperty(0);
|
||||
private ObjectProperty<EnumBackgroundImage> backgroundImageType = new SimpleObjectProperty<>(EnumBackgroundImage.DEFAULT);
|
||||
|
||||
@SerializedName("bgpath")
|
||||
public final StringProperty backgroundImage = new SimpleStringProperty();
|
||||
private StringProperty backgroundImage = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("commonDirType")
|
||||
private ObjectProperty<EnumCommonDirectory> commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT);
|
||||
|
||||
@SerializedName("commonpath")
|
||||
public final StringProperty commonDirectory = new SimpleStringProperty(Launcher.MINECRAFT_DIRECTORY.getAbsolutePath());
|
||||
private StringProperty commonDirectory = new SimpleStringProperty(Launcher.MINECRAFT_DIRECTORY.getAbsolutePath());
|
||||
|
||||
@SerializedName("hasProxy")
|
||||
public final BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
private BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("hasProxyAuth")
|
||||
public final BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
private BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("proxyType")
|
||||
public final IntegerProperty proxyType = new SimpleIntegerProperty();
|
||||
private ObjectProperty<Proxy.Type> proxyType = new SimpleObjectProperty<>(Proxy.Type.DIRECT);
|
||||
|
||||
@SerializedName("proxyHost")
|
||||
public final StringProperty proxyHost = new SimpleStringProperty();
|
||||
private StringProperty proxyHost = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPort")
|
||||
public final StringProperty proxyPort = new SimpleStringProperty();
|
||||
private StringProperty proxyPort = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyUserName")
|
||||
public final StringProperty proxyUser = new SimpleStringProperty();
|
||||
private StringProperty proxyUser = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("proxyPassword")
|
||||
public final StringProperty proxyPass = new SimpleStringProperty();
|
||||
private StringProperty proxyPass = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("theme")
|
||||
public final StringProperty theme = new SimpleStringProperty();
|
||||
private ObjectProperty<Theme> theme = new SimpleObjectProperty<>(Theme.BLUE);
|
||||
|
||||
@SerializedName("localization")
|
||||
public final StringProperty localization = new SimpleStringProperty();
|
||||
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||
|
||||
@SerializedName("downloadtype")
|
||||
public final IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
private IntegerProperty downloadType = new SimpleIntegerProperty(1);
|
||||
|
||||
@SerializedName("configurations")
|
||||
public final ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
|
||||
@SerializedName("accounts")
|
||||
public final ObservableList<Map<Object, Object>> accounts = FXCollections.observableArrayList();
|
||||
private ObservableList<Map<Object, Object>> accountStorages = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("selectedAccount")
|
||||
public final StringProperty selectedAccount = new SimpleStringProperty("");
|
||||
private StringProperty selectedAccount = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("fontFamily")
|
||||
public final StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
private StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
|
||||
@SerializedName("fontSize")
|
||||
public final DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
private DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
|
||||
@SerializedName("logLines")
|
||||
public final IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
private IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("firstLaunch")
|
||||
public final BooleanProperty firstLaunch = new SimpleBooleanProperty(true);
|
||||
@SerializedName("authlibInjectorServers")
|
||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
public Config() {
|
||||
addListenerToProperties();
|
||||
}
|
||||
|
||||
private void addListenerToProperties() {
|
||||
Stream.of(getClass().getDeclaredFields())
|
||||
.filter(it -> {
|
||||
int modifiers = it.getModifiers();
|
||||
return !Modifier.isTransient(modifiers) && !Modifier.isStatic(modifiers);
|
||||
})
|
||||
.filter(it -> Observable.class.isAssignableFrom(it.getType()))
|
||||
.map(it -> {
|
||||
try {
|
||||
return (Observable) it.get(this);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException("Failed to get my own properties");
|
||||
}
|
||||
})
|
||||
.forEach(helper::receiveUpdatesFrom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
helper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
helper.removeListener(listener);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return CONFIG_GSON.toJson(this);
|
||||
@@ -137,4 +193,246 @@ public final class Config implements Cloneable {
|
||||
public Config clone() {
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
|
||||
// Getters & Setters & Properties
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile.get();
|
||||
}
|
||||
|
||||
public void setSelectedProfile(String selectedProfile) {
|
||||
this.selectedProfile.set(selectedProfile);
|
||||
}
|
||||
|
||||
public StringProperty selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public EnumBackgroundImage getBackgroundImageType() {
|
||||
return backgroundImageType.get();
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {
|
||||
this.backgroundImageType.set(backgroundImageType);
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage.get();
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
public StringProperty backgroundImageProperty() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public EnumCommonDirectory getCommonDirType() {
|
||||
return commonDirType.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumCommonDirectory> commonDirTypeProperty() {
|
||||
return commonDirType;
|
||||
}
|
||||
|
||||
public void setCommonDirType(EnumCommonDirectory commonDirType) {
|
||||
this.commonDirType.set(commonDirType);
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
return commonDirectory.get();
|
||||
}
|
||||
|
||||
public void setCommonDirectory(String commonDirectory) {
|
||||
this.commonDirectory.set(commonDirectory);
|
||||
}
|
||||
|
||||
public StringProperty commonDirectoryProperty() {
|
||||
return commonDirectory;
|
||||
}
|
||||
|
||||
public boolean hasProxy() {
|
||||
return hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
this.hasProxy.set(hasProxy);
|
||||
}
|
||||
|
||||
public BooleanProperty hasProxyProperty() {
|
||||
return hasProxy;
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() {
|
||||
return hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
this.hasProxyAuth.set(hasProxyAuth);
|
||||
}
|
||||
|
||||
public BooleanProperty hasProxyAuthProperty() {
|
||||
return hasProxyAuth;
|
||||
}
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType.get();
|
||||
}
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType.set(proxyType);
|
||||
}
|
||||
|
||||
public ObjectProperty<Proxy.Type> proxyTypeProperty() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
this.proxyHost.set(proxyHost);
|
||||
}
|
||||
|
||||
public StringProperty proxyHostProperty() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
this.proxyPort.set(proxyPort);
|
||||
}
|
||||
|
||||
public StringProperty proxyPortProperty() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
this.proxyUser.set(proxyUser);
|
||||
}
|
||||
|
||||
public StringProperty proxyUserProperty() {
|
||||
return proxyUser;
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
this.proxyPass.set(proxyPass);
|
||||
}
|
||||
|
||||
public StringProperty proxyPassProperty() {
|
||||
return proxyPass;
|
||||
}
|
||||
|
||||
public Theme getTheme() {
|
||||
return theme.get();
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme.set(theme);
|
||||
}
|
||||
|
||||
public ObjectProperty<Theme> themeProperty() {
|
||||
return theme;
|
||||
}
|
||||
|
||||
public SupportedLocale getLocalization() {
|
||||
return localization.get();
|
||||
}
|
||||
|
||||
public void setLocalization(SupportedLocale localization) {
|
||||
this.localization.set(localization);
|
||||
}
|
||||
|
||||
public ObjectProperty<SupportedLocale> localizationProperty() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
return downloadType.get();
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadType) {
|
||||
this.downloadType.set(downloadType);
|
||||
}
|
||||
|
||||
public IntegerProperty downloadTypeProperty() {
|
||||
return downloadType;
|
||||
}
|
||||
|
||||
public ObservableMap<String, Profile> getConfigurations() {
|
||||
return configurations;
|
||||
}
|
||||
|
||||
public ObservableList<Map<Object, Object>> getAccountStorages() {
|
||||
return accountStorages;
|
||||
}
|
||||
|
||||
public String getSelectedAccount() {
|
||||
return selectedAccount.get();
|
||||
}
|
||||
|
||||
public void setSelectedAccount(String selectedAccount) {
|
||||
this.selectedAccount.set(selectedAccount);
|
||||
}
|
||||
|
||||
public StringProperty selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily.get();
|
||||
}
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily.set(fontFamily);
|
||||
}
|
||||
|
||||
public StringProperty fontFamilyProperty() {
|
||||
return fontFamily;
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
return fontSize.get();
|
||||
}
|
||||
|
||||
public void setFontSize(double fontSize) {
|
||||
this.fontSize.set(fontSize);
|
||||
}
|
||||
|
||||
public DoubleProperty fontSizeProperty() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return logLines.get();
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
this.logLines.set(logLines);
|
||||
}
|
||||
|
||||
public IntegerProperty logLinesProperty() {
|
||||
return logLines;
|
||||
}
|
||||
|
||||
public ObservableList<AuthlibInjectorServer> getAuthlibInjectorServers() {
|
||||
return authlibInjectorServers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,8 +24,10 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
public final class ConfigHolder {
|
||||
@@ -34,32 +36,72 @@ public final class ConfigHolder {
|
||||
|
||||
public static final String CONFIG_FILENAME = "hmcl.json";
|
||||
public static final Path CONFIG_PATH = Paths.get(CONFIG_FILENAME).toAbsolutePath();
|
||||
public static final Config CONFIG = initSettings();
|
||||
|
||||
private static Config initSettings() {
|
||||
Config config = new Config();
|
||||
if (Files.exists(CONFIG_PATH)) {
|
||||
try {
|
||||
Config deserialized = Config.fromJson(new String(Files.readAllBytes(CONFIG_PATH), UTF_8));
|
||||
if (deserialized == null) {
|
||||
LOG.finer("Settings file is empty, use the default settings.");
|
||||
} else {
|
||||
config = deserialized;
|
||||
}
|
||||
LOG.finest("Initialized settings.");
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
|
||||
}
|
||||
private static Config configInstance;
|
||||
private static boolean newlyCreated;
|
||||
|
||||
public static Config config() {
|
||||
if (configInstance == null) {
|
||||
throw new IllegalStateException("Configuration hasn't been loaded");
|
||||
}
|
||||
return config;
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
static void saveConfig(Config config) {
|
||||
public static boolean isNewlyCreated() {
|
||||
if (configInstance == null) {
|
||||
throw new IllegalStateException("Configuration hasn't been loaded");
|
||||
}
|
||||
return newlyCreated;
|
||||
}
|
||||
|
||||
public synchronized static void init() {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("Configuration is already loaded");
|
||||
}
|
||||
|
||||
configInstance = loadConfig();
|
||||
configInstance.addListener(source -> saveConfig());
|
||||
|
||||
Settings.init();
|
||||
|
||||
if (newlyCreated) {
|
||||
saveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private static Config loadConfig() {
|
||||
if (Files.exists(CONFIG_PATH)) {
|
||||
try {
|
||||
String content = new String(Files.readAllBytes(CONFIG_PATH), UTF_8);
|
||||
Config deserialized = Config.fromJson(content);
|
||||
if (deserialized == null) {
|
||||
LOG.info("Config is empty");
|
||||
} else {
|
||||
Map<?, ?> raw = new Gson().fromJson(content, Map.class);
|
||||
return upgradeConfig(deserialized, raw);
|
||||
}
|
||||
} catch (IOException | JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Something went wrong when loading config.", e);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Creating an empty config");
|
||||
newlyCreated = true;
|
||||
return new Config();
|
||||
}
|
||||
|
||||
static void saveConfig() {
|
||||
LOG.info("Saving config");
|
||||
try {
|
||||
Files.write(CONFIG_PATH, config.toJson().getBytes(UTF_8));
|
||||
Files.write(CONFIG_PATH, configInstance.toJson().getBytes(UTF_8));
|
||||
} catch (IOException ex) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Config upgradeConfig(Config deserialized, Map<?, ?> rawJson) {
|
||||
if (!rawJson.containsKey("commonDirType"))
|
||||
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
|
||||
return deserialized;
|
||||
}
|
||||
}
|
||||
+15
-18
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* Copyright (C) 2017 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
@@ -17,20 +17,17 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Proxies {
|
||||
private Proxies() {}
|
||||
|
||||
public static final List<Proxy.Type> PROXIES = Lang.immutableListOf(Proxy.Type.DIRECT, Proxy.Type.HTTP, Proxy.Type.SOCKS);
|
||||
|
||||
public static Proxy.Type getProxyType(int index) {
|
||||
return Lang.get(PROXIES, index).orElse(null);
|
||||
}
|
||||
public enum EnumCommonDirectory {
|
||||
/**
|
||||
* same to game directory
|
||||
*/
|
||||
DISABLED,
|
||||
/**
|
||||
* %appdata%/.minecraft or ~/.minecraft
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* user customized common directory.
|
||||
*/
|
||||
CUSTOM
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public final class Profile {
|
||||
}
|
||||
|
||||
public HMCLDependencyManager getDependency() {
|
||||
return new HMCLDependencyManager(this, Settings.INSTANCE.getDownloadProvider(), Settings.INSTANCE.getProxy());
|
||||
return new HMCLDependencyManager(this, Settings.instance().getDownloadProvider());
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
|
||||
public final class ProxyManager {
|
||||
private ProxyManager() {
|
||||
}
|
||||
|
||||
private static ObjectBinding<Proxy> proxyProperty = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
String host = config().getProxyHost();
|
||||
Integer port = Lang.toIntOrNull(config().getProxyPort());
|
||||
if (!config().hasProxy() || StringUtils.isBlank(host) || port == null || config().getProxyType() == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY;
|
||||
} else {
|
||||
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
||||
}
|
||||
},
|
||||
config().proxyTypeProperty(),
|
||||
config().proxyHostProperty(),
|
||||
config().proxyPortProperty(),
|
||||
config().hasProxyProperty());
|
||||
|
||||
public static Proxy getProxy() {
|
||||
return proxyProperty.get();
|
||||
}
|
||||
|
||||
public static ObservableObjectValue<Proxy> proxyProperty() {
|
||||
return proxyProperty;
|
||||
}
|
||||
|
||||
static void init() {
|
||||
proxyProperty.addListener(observable -> updateSystemProxy());
|
||||
|
||||
updateSystemProxy();
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
if (config().hasProxyAuth()) {
|
||||
String username = config().getProxyUser();
|
||||
String password = config().getProxyPass();
|
||||
if (username != null && password != null) {
|
||||
return new PasswordAuthentication(username, password.toCharArray());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void updateSystemProxy() {
|
||||
Proxy proxy = proxyProperty.get();
|
||||
if (proxy.type() == Proxy.Type.DIRECT) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
} else {
|
||||
InetSocketAddress address = (InetSocketAddress) proxy.address();
|
||||
String host = address.getHostString();
|
||||
String port = String.valueOf(address.getPort());
|
||||
if (proxy.type() == Type.HTTP) {
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
System.setProperty("http.proxyHost", host);
|
||||
System.setProperty("http.proxyPort", port);
|
||||
System.setProperty("https.proxyHost", host);
|
||||
System.setProperty("https.proxyPort", port);
|
||||
} else if (proxy.type() == Type.SOCKS) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.setProperty("socksProxyHost", host);
|
||||
System.setProperty("socksProxyPort", port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,269 +17,89 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.event.*;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class Settings {
|
||||
|
||||
public static final Settings INSTANCE = new Settings();
|
||||
private static Settings instance;
|
||||
|
||||
private final Map<String, Account> accounts = new ConcurrentHashMap<>();
|
||||
public static Settings instance() {
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("Settings hasn't been initialized");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private final boolean firstLaunch;
|
||||
/**
|
||||
* Should be called from {@link ConfigHolder#init()}.
|
||||
*/
|
||||
static void init() {
|
||||
instance = new Settings();
|
||||
}
|
||||
|
||||
private Settings() {
|
||||
firstLaunch = ConfigHolder.CONFIG.firstLaunch.get();
|
||||
ConfigHolder.CONFIG.firstLaunch.set(false);
|
||||
|
||||
loadProxy();
|
||||
|
||||
for (Iterator<Map<Object, Object>> iterator = ConfigHolder.CONFIG.accounts.iterator(); iterator.hasNext();) {
|
||||
Map<Object, Object> settings = iterator.next();
|
||||
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
|
||||
if (factory == null) {
|
||||
LOG.warning("Unrecognized account type, removing: " + settings);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
Account account;
|
||||
try {
|
||||
account = factory.fromStorage(settings, getProxy());
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Malformed account storage, removing: " + settings, e);
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
accounts.put(Accounts.getAccountId(account), account);
|
||||
}
|
||||
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
this.selectedAccount.set(accounts.get(ConfigHolder.CONFIG.selectedAccount.get()));
|
||||
ProxyManager.init();
|
||||
Accounts.init();
|
||||
|
||||
checkProfileMap();
|
||||
|
||||
save();
|
||||
|
||||
for (Map.Entry<String, Profile> entry2 : getProfileMap().entrySet()) {
|
||||
entry2.getValue().setName(entry2.getKey());
|
||||
entry2.getValue().nameProperty().setChangedListener(this::profileNameChanged);
|
||||
entry2.getValue().addPropertyChangedListener(e -> save());
|
||||
for (Map.Entry<String, Profile> profileEntry : getProfileMap().entrySet()) {
|
||||
profileEntry.getValue().setName(profileEntry.getKey());
|
||||
profileEntry.getValue().nameProperty().setChangedListener(this::profileNameChanged);
|
||||
profileEntry.getValue().addPropertyChangedListener(e -> ConfigHolder.saveConfig());
|
||||
}
|
||||
|
||||
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
|
||||
}
|
||||
|
||||
public void save() {
|
||||
ConfigHolder.CONFIG.accounts.clear();
|
||||
for (Account account : accounts.values()) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", Accounts.getAccountType(account));
|
||||
ConfigHolder.CONFIG.accounts.add(storage);
|
||||
}
|
||||
ConfigHolder.saveConfig(ConfigHolder.CONFIG);
|
||||
}
|
||||
|
||||
public boolean isFirstLaunch() {
|
||||
return firstLaunch;
|
||||
}
|
||||
|
||||
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", ConfigHolder.CONFIG.commonDirectory.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
ConfigHolder.CONFIG.commonDirectory.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public String getCommonPath() {
|
||||
return commonPath.get();
|
||||
}
|
||||
|
||||
public StringProperty commonPathProperty() {
|
||||
return commonPath;
|
||||
}
|
||||
|
||||
public void setCommonPath(String commonPath) {
|
||||
this.commonPath.set(commonPath);
|
||||
}
|
||||
|
||||
private Locales.SupportedLocale locale = Locales.getLocaleByName(ConfigHolder.CONFIG.localization.get());
|
||||
|
||||
public Locales.SupportedLocale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(Locales.SupportedLocale locale) {
|
||||
this.locale = locale;
|
||||
ConfigHolder.CONFIG.localization.set(Locales.getNameByLocale(locale));
|
||||
save();
|
||||
}
|
||||
|
||||
private Proxy proxy = Proxy.NO_PROXY;
|
||||
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private Proxy.Type proxyType = Proxies.getProxyType(ConfigHolder.CONFIG.proxyType.get());
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType = proxyType;
|
||||
ConfigHolder.CONFIG.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
|
||||
save();
|
||||
loadProxy();
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return ConfigHolder.CONFIG.proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
ConfigHolder.CONFIG.proxyHost.set(proxyHost);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPort() {
|
||||
return ConfigHolder.CONFIG.proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(String proxyPort) {
|
||||
ConfigHolder.CONFIG.proxyPort.set(proxyPort);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return ConfigHolder.CONFIG.proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
ConfigHolder.CONFIG.proxyUser.set(proxyUser);
|
||||
save();
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return ConfigHolder.CONFIG.proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
ConfigHolder.CONFIG.proxyPass.set(proxyPass);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxy() {
|
||||
return ConfigHolder.CONFIG.hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
ConfigHolder.CONFIG.hasProxy.set(hasProxy);
|
||||
save();
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() {
|
||||
return ConfigHolder.CONFIG.hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
ConfigHolder.CONFIG.hasProxyAuth.set(hasProxyAuth);
|
||||
save();
|
||||
}
|
||||
|
||||
private void loadProxy() {
|
||||
String host = getProxyHost();
|
||||
Integer port = Lang.toIntOrNull(getProxyPort());
|
||||
if (!hasProxy() || StringUtils.isBlank(host) || port == null || getProxyType() == Proxy.Type.DIRECT)
|
||||
proxy = Proxy.NO_PROXY;
|
||||
else {
|
||||
System.setProperty("http.proxyHost", getProxyHost());
|
||||
System.setProperty("http.proxyPort", getProxyPort());
|
||||
proxy = new Proxy(proxyType, new InetSocketAddress(host, port));
|
||||
|
||||
String user = getProxyUser();
|
||||
String pass = getProxyPass();
|
||||
if (hasProxyAuth() && StringUtils.isNotBlank(user) && StringUtils.isNotBlank(pass)) {
|
||||
System.setProperty("http.proxyUser", user);
|
||||
System.setProperty("http.proxyPassword", pass);
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
public PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(user, pass.toCharArray());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
config().addListener(source -> ConfigHolder.saveConfig());
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return Font.font(ConfigHolder.CONFIG.fontFamily.get(), ConfigHolder.CONFIG.fontSize.get());
|
||||
return Font.font(config().getFontFamily(), config().getFontSize());
|
||||
}
|
||||
|
||||
public void setFont(Font font) {
|
||||
ConfigHolder.CONFIG.fontFamily.set(font.getFamily());
|
||||
ConfigHolder.CONFIG.fontSize.set(font.getSize());
|
||||
save();
|
||||
config().setFontFamily(font.getFamily());
|
||||
config().setFontSize(font.getSize());
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return Math.max(ConfigHolder.CONFIG.logLines.get(), 100);
|
||||
return Math.max(config().getLogLines(), 100);
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
ConfigHolder.CONFIG.logLines.set(logLines);
|
||||
save();
|
||||
config().setLogLines(logLines);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* AUTHLIB INJECTORS *
|
||||
****************************************/
|
||||
public boolean isCommonDirectoryDisabled() {
|
||||
return config().getCommonDirType() == EnumCommonDirectory.DISABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
|
||||
* This method performs a check and removes the dangling accounts.
|
||||
* Don't call this before {@link #migrateAuthlibInjectorServers()} is called, otherwise old data would be lost.
|
||||
*/
|
||||
private void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.values().stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.filter(it -> !ConfigHolder.CONFIG.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
|
||||
.collect(toList())
|
||||
.forEach(this::deleteAccount);
|
||||
public static String getDefaultCommonDirectory() {
|
||||
return Launcher.MINECRAFT_DIRECTORY.getAbsolutePath();
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
switch (config().getCommonDirType()) {
|
||||
case DISABLED:
|
||||
return null;
|
||||
case DEFAULT:
|
||||
return getDefaultCommonDirectory();
|
||||
case CUSTOM:
|
||||
return config().getCommonDirectory();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************
|
||||
@@ -287,161 +107,14 @@ public class Settings {
|
||||
****************************************/
|
||||
|
||||
public DownloadProvider getDownloadProvider() {
|
||||
return DownloadProviders.getDownloadProvider(ConfigHolder.CONFIG.downloadType.get());
|
||||
return DownloadProviders.getDownloadProvider(config().getDownloadType());
|
||||
}
|
||||
|
||||
public void setDownloadProvider(DownloadProvider downloadProvider) {
|
||||
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
|
||||
if (index == -1)
|
||||
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
|
||||
ConfigHolder.CONFIG.downloadType.set(index);
|
||||
save();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* ACCOUNTS *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", null) {
|
||||
@Override
|
||||
public Account get() {
|
||||
Account a = super.get();
|
||||
if (a == null || !accounts.containsKey(Accounts.getAccountId(a))) {
|
||||
Account acc = accounts.values().stream().findAny().orElse(null);
|
||||
set(acc);
|
||||
return acc;
|
||||
} else return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(Account newValue) {
|
||||
if (newValue == null || accounts.containsKey(Accounts.getAccountId(newValue))) {
|
||||
super.set(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
ConfigHolder.CONFIG.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public Account getSelectedAccount() {
|
||||
return selectedAccount.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public void setSelectedAccount(Account selectedAccount) {
|
||||
this.selectedAccount.set(selectedAccount);
|
||||
}
|
||||
|
||||
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) {
|
||||
return accounts.get(Accounts.getAccountId(name, character));
|
||||
}
|
||||
|
||||
public Collection<Account> getAccounts() {
|
||||
return Collections.unmodifiableCollection(accounts.values());
|
||||
}
|
||||
|
||||
public void deleteAccount(String name, String character) {
|
||||
accounts.remove(Accounts.getAccountId(name, character));
|
||||
|
||||
onAccountLoading();
|
||||
selectedAccount.get();
|
||||
}
|
||||
|
||||
public void deleteAccount(Account account) {
|
||||
accounts.remove(Accounts.getAccountId(account));
|
||||
|
||||
onAccountLoading();
|
||||
selectedAccount.get();
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* BACKGROUND *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", ConfigHolder.CONFIG.backgroundImage.get()) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
ConfigHolder.CONFIG.backgroundImage.set(get());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage.get();
|
||||
}
|
||||
|
||||
public ImmediateStringProperty backgroundImageProperty() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(ConfigHolder.CONFIG.backgroundImageType.get())) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
ConfigHolder.CONFIG.backgroundImageType.set(get().ordinal());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public EnumBackgroundImage getBackgroundImageType() {
|
||||
return backgroundImageType.get();
|
||||
}
|
||||
|
||||
public ImmediateObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {
|
||||
this.backgroundImageType.set(backgroundImageType);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
* THEME *
|
||||
****************************************/
|
||||
|
||||
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(ConfigHolder.CONFIG.theme.get()).orElse(Theme.BLUE)) {
|
||||
@Override
|
||||
public void invalidated() {
|
||||
super.invalidated();
|
||||
|
||||
ConfigHolder.CONFIG.theme.set(get().getName().toLowerCase());
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
public Theme getTheme() {
|
||||
return theme.get();
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme.set(theme);
|
||||
}
|
||||
|
||||
public ImmediateObjectProperty<Theme> themeProperty() {
|
||||
return theme;
|
||||
config().setDownloadType(index);
|
||||
}
|
||||
|
||||
/****************************************
|
||||
@@ -451,20 +124,18 @@ public class Settings {
|
||||
public Profile getSelectedProfile() {
|
||||
checkProfileMap();
|
||||
|
||||
if (!hasProfile(ConfigHolder.CONFIG.selectedProfile.get())) {
|
||||
if (!hasProfile(config().getSelectedProfile())) {
|
||||
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
|
||||
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile);
|
||||
save();
|
||||
config().setSelectedProfile(selectedProfile);
|
||||
});
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
return getProfile(ConfigHolder.CONFIG.selectedProfile.get());
|
||||
return getProfile(config().getSelectedProfile());
|
||||
}
|
||||
|
||||
public void setSelectedProfile(Profile selectedProfile) {
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), ConfigHolder.CONFIG.selectedProfile.get())) {
|
||||
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile.getName());
|
||||
save();
|
||||
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), config().getSelectedProfile())) {
|
||||
config().setSelectedProfile(selectedProfile.getName());
|
||||
Schedulers.computation().schedule(this::onProfileChanged);
|
||||
}
|
||||
}
|
||||
@@ -481,7 +152,7 @@ public class Settings {
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfileMap() {
|
||||
return ConfigHolder.CONFIG.configurations;
|
||||
return config().getConfigurations();
|
||||
}
|
||||
|
||||
public Collection<Profile> getProfiles() {
|
||||
@@ -496,8 +167,6 @@ public class Settings {
|
||||
Schedulers.computation().schedule(this::onProfileLoading);
|
||||
|
||||
ver.nameProperty().setChangedListener(this::profileNameChanged);
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
public void deleteProfile(Profile profile) {
|
||||
@@ -538,12 +207,4 @@ public class Settings {
|
||||
EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(this, getProfiles()));
|
||||
onProfileChanged();
|
||||
}
|
||||
|
||||
public void onAccountLoading() {
|
||||
EventBus.EVENT_BUS.fireEvent(new AccountLoadingEvent(this, getAccounts()));
|
||||
}
|
||||
|
||||
public Config getRawConfig() {
|
||||
return ConfigHolder.CONFIG.clone();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.IOUtils;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
@@ -115,7 +120,7 @@ public class Theme {
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> foregroundFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> Settings.INSTANCE.getTheme().getForegroundColor(), Settings.INSTANCE.themeProperty());
|
||||
return Bindings.createObjectBinding(() -> config().getTheme().getForegroundColor(), config().themeProperty());
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> blackFillBinding() {
|
||||
@@ -125,4 +130,16 @@ public class Theme {
|
||||
public static ObjectBinding<Color> whiteFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> Color.WHITE);
|
||||
}
|
||||
|
||||
public static class TypeAdapter extends com.google.gson.TypeAdapter<Theme> {
|
||||
@Override
|
||||
public void write(JsonWriter out, Theme value) throws IOException {
|
||||
out.value(value.getName().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Theme read(JsonReader in) throws IOException {
|
||||
return getTheme(in.nextString()).orElse(Theme.BLUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -79,7 +81,7 @@ public final class VersionSetting {
|
||||
}
|
||||
|
||||
/**
|
||||
* Java version or null if user customizes java directory.
|
||||
* Java version or "Custom" if user customizes java directory, "Default" if the jvm that this app relies on.
|
||||
*/
|
||||
public String getJava() {
|
||||
return javaProperty.get();
|
||||
@@ -89,6 +91,15 @@ public final class VersionSetting {
|
||||
javaProperty.set(java);
|
||||
}
|
||||
|
||||
public boolean isUsesCustomJavaDir() {
|
||||
return "Custom".equals(getJava());
|
||||
}
|
||||
|
||||
public void setUsesCustomJavaDir() {
|
||||
setJava("Custom");
|
||||
setDefaultJavaPath(null);
|
||||
}
|
||||
|
||||
private final ImmediateStringProperty defaultJavaPathProperty = new ImmediateStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
@@ -276,23 +287,6 @@ public final class VersionSetting {
|
||||
notCheckGameProperty.set(notCheckGame);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty noCommonProperty = new ImmediateBooleanProperty(this, "noCommon", false);
|
||||
|
||||
public ImmediateBooleanProperty noCommonProperty() {
|
||||
return noCommonProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if HMCL does not find/download libraries in/to common path.
|
||||
*/
|
||||
public boolean isNoCommon() {
|
||||
return noCommonProperty.get();
|
||||
}
|
||||
|
||||
public void setNoCommon(boolean noCommon) {
|
||||
noCommonProperty.set(noCommon);
|
||||
}
|
||||
|
||||
private final ImmediateBooleanProperty showLogsProperty = new ImmediateBooleanProperty(this, "showLogs", false);
|
||||
|
||||
public ImmediateBooleanProperty showLogsProperty() {
|
||||
@@ -453,7 +447,7 @@ public final class VersionSetting {
|
||||
if (StringUtils.isBlank(getJava()))
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Default" : "Custom");
|
||||
if ("Default".equals(getJava())) return JavaVersion.fromCurrentEnvironment();
|
||||
else if ("Custom".equals(getJava())) {
|
||||
else if (isUsesCustomJavaDir()) {
|
||||
try {
|
||||
return JavaVersion.fromExecutable(new File(getJavaDir()));
|
||||
} catch (IOException e) {
|
||||
@@ -475,6 +469,11 @@ public final class VersionSetting {
|
||||
} else throw new Error();
|
||||
}
|
||||
|
||||
public void setJavaVersion(JavaVersion java) {
|
||||
setJava(java.getVersion());
|
||||
setDefaultJavaPath(java.getBinary().toString());
|
||||
}
|
||||
|
||||
public void addPropertyChangedListener(InvalidationListener listener) {
|
||||
usesGlobalProperty.addListener(listener);
|
||||
javaProperty.addListener(listener);
|
||||
@@ -488,7 +487,6 @@ public final class VersionSetting {
|
||||
minecraftArgsProperty.addListener(listener);
|
||||
noJVMArgsProperty.addListener(listener);
|
||||
notCheckGameProperty.addListener(listener);
|
||||
noCommonProperty.addListener(listener);
|
||||
showLogsProperty.addListener(listener);
|
||||
serverIpProperty.addListener(listener);
|
||||
fullscreenProperty.addListener(listener);
|
||||
@@ -517,10 +515,10 @@ public final class VersionSetting {
|
||||
.setFullscreen(isFullscreen())
|
||||
.setServerIp(getServerIp())
|
||||
.setWrapper(getWrapper())
|
||||
.setProxyHost(Settings.INSTANCE.getProxyHost())
|
||||
.setProxyPort(Settings.INSTANCE.getProxyPort())
|
||||
.setProxyUser(Settings.INSTANCE.getProxyUser())
|
||||
.setProxyPass(Settings.INSTANCE.getProxyPass())
|
||||
.setProxyHost(config().getProxyHost())
|
||||
.setProxyPort(config().getProxyPort())
|
||||
.setProxyUser(config().getProxyUser())
|
||||
.setProxyPass(config().getProxyPass())
|
||||
.setPrecalledCommand(getPreLaunchCommand())
|
||||
.setNoGeneratedJVMArgs(isNoJVMArgs())
|
||||
.create();
|
||||
@@ -553,7 +551,6 @@ public final class VersionSetting {
|
||||
obj.addProperty("fullscreen", src.isFullscreen());
|
||||
obj.addProperty("noJVMArgs", src.isNoJVMArgs());
|
||||
obj.addProperty("notCheckGame", src.isNotCheckGame());
|
||||
obj.addProperty("noCommon", src.isNoCommon());
|
||||
obj.addProperty("showLogs", src.isShowLogs());
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
@@ -591,7 +588,6 @@ public final class VersionSetting {
|
||||
vs.setFullscreen(Optional.ofNullable(obj.get("fullscreen")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoJVMArgs(Optional.ofNullable(obj.get("noJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckGame(Optional.ofNullable(obj.get("notCheckGame")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoCommon(Optional.ofNullable(obj.get("noCommon")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
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)]);
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
@@ -97,7 +97,7 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), 15, 15));
|
||||
|
||||
lblCharacter.setText(account.getCharacter());
|
||||
lblType.setText(AddAccountPane.accountType(account));
|
||||
lblType.setText(Accounts.getAccountTypeName(account));
|
||||
lblEmail.setText(account.getUsername());
|
||||
|
||||
btnRefresh.setVisible(account instanceof YggdrasilAccount);
|
||||
@@ -105,7 +105,7 @@ public class AccountPage extends StackPane implements DecoratorPage {
|
||||
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
Settings.INSTANCE.deleteAccount(account);
|
||||
Accounts.getAccounts().remove(account);
|
||||
Optional.ofNullable(onDelete.get()).ifPresent(Runnable::run);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
@@ -33,16 +33,12 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.AccountHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
@@ -52,23 +48,25 @@ import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.util.Constants;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AddAccountPane extends StackPane {
|
||||
|
||||
@FXML private JFXTextField txtUsername;
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblPassword;
|
||||
@FXML private JFXComboBox<String> cboType;
|
||||
@FXML private JFXComboBox<AccountFactory<?>> cboType;
|
||||
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
|
||||
@FXML private Label lblInjectorServer;
|
||||
@FXML private Hyperlink linkManageInjectorServers;
|
||||
@@ -81,27 +79,35 @@ public class AddAccountPane extends StackPane {
|
||||
|
||||
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
||||
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
||||
Bindings.bindContent(cboServers.getItems(), ConfigHolder.CONFIG.authlibInjectorServers);
|
||||
Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers());
|
||||
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
|
||||
selectDefaultServer();
|
||||
|
||||
cboType.getItems().setAll(i18n("account.methods.offline"), i18n("account.methods.yggdrasil"), i18n("account.methods.authlib_injector"));
|
||||
cboType.getItems().setAll(Accounts.FACTORY_OFFLINE, Accounts.FACTORY_YGGDRASIL, Accounts.FACTORY_AUTHLIB_INJECTOR);
|
||||
cboType.setConverter(stringConverter(Accounts::getAccountTypeName));
|
||||
cboType.getSelectionModel().select(0);
|
||||
|
||||
ReadOnlyIntegerProperty loginTypeIdProperty = cboType.getSelectionModel().selectedIndexProperty();
|
||||
ReadOnlyObjectProperty<AccountFactory<?>> loginType = cboType.getSelectionModel().selectedItemProperty();
|
||||
|
||||
txtPassword.visibleProperty().bind(loginTypeIdProperty.isNotEqualTo(0));
|
||||
txtPassword.visibleProperty().bind(loginType.isNotEqualTo(Accounts.FACTORY_OFFLINE));
|
||||
lblPassword.visibleProperty().bind(txtPassword.visibleProperty());
|
||||
|
||||
cboServers.visibleProperty().bind(loginTypeIdProperty.isEqualTo(2));
|
||||
cboServers.visibleProperty().bind(loginType.isEqualTo(Accounts.FACTORY_AUTHLIB_INJECTOR));
|
||||
lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
linkManageInjectorServers.visibleProperty().bind(cboServers.visibleProperty());
|
||||
|
||||
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
|
||||
btnAccept.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !txtUsername.validate() || (loginTypeIdProperty.get() != 0 && !txtPassword.validate()),
|
||||
txtUsername.textProperty(), txtPassword.textProperty(), loginTypeIdProperty));
|
||||
() -> !( // consider the opposite situation: input is valid
|
||||
txtUsername.validate() &&
|
||||
// invisible means the field is not needed, neither should it be validated
|
||||
(!txtPassword.isVisible() || txtPassword.validate()) &&
|
||||
(!cboServers.isVisible() || cboServers.getSelectionModel().getSelectedItem() != null)
|
||||
),
|
||||
txtUsername.textProperty(),
|
||||
txtPassword.textProperty(), txtPassword.visibleProperty(),
|
||||
cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,47 +119,48 @@ public class AddAccountPane extends StackPane {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional data that needs to be passed into {@link AccountFactory#create(CharacterSelector, String, String, Object)}.
|
||||
*/
|
||||
private Object getAuthAdditionalData() {
|
||||
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
|
||||
if (factory == Accounts.FACTORY_AUTHLIB_INJECTOR) {
|
||||
return requireNonNull(cboServers.getSelectionModel().getSelectedItem(), "selected server cannot be null");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationAccept() {
|
||||
if (btnAccept.isDisabled())
|
||||
return;
|
||||
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
Object addtionalData;
|
||||
|
||||
int type = cboType.getSelectionModel().getSelectedIndex();
|
||||
AccountFactory<?> factory;
|
||||
switch (type) {
|
||||
case 0:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 1:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY);
|
||||
addtionalData = null;
|
||||
break;
|
||||
case 2:
|
||||
factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY);
|
||||
Optional<AuthlibInjectorServer> server = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem());
|
||||
if (server.isPresent()) {
|
||||
addtionalData = server.get();
|
||||
} else {
|
||||
lblCreationWarning.setText(i18n("account.failed.no_selected_server"));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
acceptPane.showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
setDisable(true);
|
||||
|
||||
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, addtionalData, Settings.INSTANCE.getProxy()))
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
|
||||
Object additionalData = getAuthAdditionalData();
|
||||
|
||||
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, additionalData))
|
||||
.finalized(Schedulers.javafx(), variables -> {
|
||||
Settings.INSTANCE.addAccount(variables.get("create_account"));
|
||||
|
||||
Account account = variables.get("create_account");
|
||||
int oldIndex = Accounts.getAccounts().indexOf(account);
|
||||
if (oldIndex == -1) {
|
||||
Accounts.getAccounts().add(account);
|
||||
} else {
|
||||
// adding an already-added account
|
||||
// instead of discarding the new account, we first remove the existing one then add the new one
|
||||
Accounts.getAccounts().remove(oldIndex);
|
||||
Accounts.getAccounts().add(oldIndex, account);
|
||||
}
|
||||
|
||||
// select the new account
|
||||
Accounts.setSelectedAccount(account);
|
||||
|
||||
acceptPane.hideSpinner();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, exception -> {
|
||||
@@ -211,7 +218,7 @@ public class AddAccountPane extends StackPane {
|
||||
for (GameProfile profile : names) {
|
||||
Image image;
|
||||
try {
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy());
|
||||
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4);
|
||||
} catch (Exception e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to get skin for " + profile.getName(), e);
|
||||
image = null;
|
||||
@@ -256,6 +263,8 @@ public class AddAccountPane extends StackPane {
|
||||
return i18n("account.failed.no_character");
|
||||
} else if (exception instanceof ServerDisconnectException) {
|
||||
return i18n("account.failed.connect_authentication_server");
|
||||
} else if (exception instanceof ServerResponseMalformedException) {
|
||||
return i18n("account.failed.server_response_malformed");
|
||||
} else if (exception instanceof RemoteAuthenticationException) {
|
||||
RemoteAuthenticationException remoteException = (RemoteAuthenticationException) exception;
|
||||
String remoteMessage = remoteException.getRemoteMessage();
|
||||
@@ -272,11 +281,4 @@ public class AddAccountPane extends StackPane {
|
||||
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
|
||||
else if (account instanceof AuthlibInjectorAccount) return i18n("account.methods.authlib_injector");
|
||||
else if (account instanceof YggdrasilAccount) return i18n("account.methods.yggdrasil");
|
||||
else throw new Error(i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
@@ -40,6 +39,8 @@ import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
|
||||
@FXML private StackPane addServerContainer;
|
||||
@@ -132,8 +133,8 @@ public class AddAuthlibInjectorServerPane extends StackPane {
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
if (!ConfigHolder.CONFIG.authlibInjectorServers.contains(serverBeingAdded)) {
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.add(serverBeingAdded);
|
||||
if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {
|
||||
config().getAuthlibInjectorServers().add(serverBeingAdded);
|
||||
}
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@@ -17,23 +17,25 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.manage.title"));
|
||||
|
||||
@@ -41,26 +43,21 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
|
||||
@FXML private VBox listPane;
|
||||
@FXML private StackPane contentPane;
|
||||
|
||||
private InvalidationListener serversListener;
|
||||
private ObservableList<AuthlibInjectorServerItem> serverItems;
|
||||
|
||||
public AuthlibInjectorServersPage() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
smoothScrolling(scrollPane);
|
||||
|
||||
serversListener = observable -> updateServersList();
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.addListener(new WeakInvalidationListener(serversListener));
|
||||
updateServersList();
|
||||
serverItems = MappedObservableList.create(config().getAuthlibInjectorServers(), this::createServerItem);
|
||||
Bindings.bindContent(listPane.getChildren(), serverItems);
|
||||
}
|
||||
|
||||
private void updateServersList() {
|
||||
listPane.getChildren().setAll(
|
||||
ConfigHolder.CONFIG.authlibInjectorServers.stream()
|
||||
.map(server -> new AuthlibInjectorServerItem(server,
|
||||
item -> ConfigHolder.CONFIG.authlibInjectorServers.remove(item.getServer())))
|
||||
.collect(toList()));
|
||||
private AuthlibInjectorServerItem createServerItem(AuthlibInjectorServer server) {
|
||||
return new AuthlibInjectorServerItem(server,
|
||||
item -> config().getAuthlibInjectorServers().remove(item.getServer()));
|
||||
}
|
||||
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
|
||||
@@ -34,6 +34,8 @@ import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.JavaVersion;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class Controllers {
|
||||
@@ -100,13 +102,13 @@ public final class Controllers {
|
||||
decorator.showPage(null);
|
||||
leftPaneController = new LeftPaneController(decorator.getLeftPane());
|
||||
|
||||
Settings.INSTANCE.onProfileLoading();
|
||||
Settings.instance().onProfileLoading();
|
||||
Task.of(JavaVersion::initialize).start();
|
||||
|
||||
decorator.setCustomMaximize(false);
|
||||
|
||||
scene = new Scene(decorator, 804, 521);
|
||||
scene.getStylesheets().setAll(Settings.INSTANCE.getTheme().getStylesheets());
|
||||
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
||||
stage.setMinWidth(804);
|
||||
stage.setMaxWidth(804);
|
||||
stage.setMinHeight(521);
|
||||
|
||||
@@ -55,8 +55,8 @@ import javafx.stage.StageStyle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
|
||||
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
@@ -66,15 +66,22 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer {
|
||||
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
|
||||
@@ -192,7 +199,7 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
);
|
||||
nowAnimation.play();
|
||||
});
|
||||
if (!Settings.INSTANCE.isFirstLaunch() || Settings.INSTANCE.getLocale().getLocale() != Locale.CHINA)
|
||||
if (!ConfigHolder.isNewlyCreated() || config().getLocalization().getLocale() != Locale.CHINA)
|
||||
drawerWrapper.getChildren().remove(welcomeView);
|
||||
|
||||
if (!min) buttonsContainer.getChildren().remove(btnMin);
|
||||
@@ -210,81 +217,87 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
|
||||
|
||||
animationHandler = new TransitionHandler(contentPlaceHolder);
|
||||
|
||||
loadBackground();
|
||||
|
||||
setupBackground();
|
||||
setupAuthlibInjectorDnD();
|
||||
}
|
||||
|
||||
private void loadBackground() {
|
||||
// ==== Background ====
|
||||
private void setupBackground() {
|
||||
drawerWrapper.backgroundProperty().bind(
|
||||
Bindings.createObjectBinding(
|
||||
() -> {
|
||||
Image image = null;
|
||||
if (config().getBackgroundImageType() == EnumBackgroundImage.CUSTOM && config().getBackgroundImage() != null) {
|
||||
image = tryLoadImage(Paths.get(config().getBackgroundImage()))
|
||||
.orElse(null);
|
||||
}
|
||||
if (image == null) {
|
||||
image = loadDefaultBackgroundImage();
|
||||
}
|
||||
return new Background(new BackgroundImage(image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true)));
|
||||
},
|
||||
config().backgroundImageTypeProperty(),
|
||||
config().backgroundImageProperty()));
|
||||
}
|
||||
|
||||
private Image defaultBackground = new Image("/assets/img/background.jpg");
|
||||
|
||||
/**
|
||||
* Load background image from bg/, background.png, background.jpg
|
||||
*/
|
||||
private Image loadDefaultBackgroundImage() {
|
||||
Optional<Image> image = randomImageIn(Paths.get("bg"));
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.png"));
|
||||
}
|
||||
if (!image.isPresent()) {
|
||||
image = tryLoadImage(Paths.get("background.jpg"));
|
||||
}
|
||||
return image.orElse(defaultBackground);
|
||||
}
|
||||
|
||||
private Optional<Image> randomImageIn(Path imageDir) {
|
||||
if (!Files.isDirectory(imageDir)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<Path> candidates;
|
||||
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());
|
||||
|
||||
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) {
|
||||
candidates = Files.list(imageDir)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(it -> {
|
||||
String filename = it.getFileName().toString();
|
||||
return filename.endsWith(".png") || filename.endsWith(".jpg");
|
||||
})
|
||||
.collect(toList());
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to list files in ./bg", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Random rnd = new Random();
|
||||
while (candidates.size() > 0) {
|
||||
int selected = rnd.nextInt(candidates.size());
|
||||
Optional<Image> loaded = tryLoadImage(candidates.get(selected));
|
||||
if (loaded.isPresent()) {
|
||||
return Optional.of(loaded.get());
|
||||
} else {
|
||||
candidates.remove(selected);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Image searchBackgroundImage(Image def, String customPath) {
|
||||
Random random = new Random();
|
||||
boolean loaded = false;
|
||||
Image background = def;
|
||||
|
||||
// custom path
|
||||
if (StringUtils.isNotBlank(customPath)) {
|
||||
private Optional<Image> tryLoadImage(Path path) {
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
background = new Image("file:" + customPath);
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// images in ./bg
|
||||
if (!loaded) {
|
||||
File backgroundImageFile = new File("bg");
|
||||
if (backgroundImageFile.isDirectory()) {
|
||||
File[] backgroundPath = backgroundImageFile.listFiles(file -> StringUtils.containsOne(FileUtils.getExtension(file), "png", "jpg"));
|
||||
if (backgroundPath != null && backgroundPath.length > 0) {
|
||||
int index = random.nextInt(backgroundPath.length);
|
||||
try {
|
||||
background = new Image("file:" + backgroundPath[index].getAbsolutePath());
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// background.png
|
||||
if (!loaded) {
|
||||
File backgroundImageFile = new File("background.png");
|
||||
if (backgroundImageFile.exists()) {
|
||||
try {
|
||||
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// background.jpg
|
||||
if (!loaded) {
|
||||
File backgroundImageFile = new File("background.jpg");
|
||||
if (backgroundImageFile.exists()) {
|
||||
try {
|
||||
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
|
||||
loaded = true;
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return background;
|
||||
return Optional.empty();
|
||||
}
|
||||
// ====
|
||||
|
||||
@FXML
|
||||
private void onMouseMoved(MouseEvent mouseEvent) {
|
||||
|
||||
@@ -21,6 +21,12 @@ import com.jfoenix.concurrency.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.Image;
|
||||
@@ -49,21 +55,39 @@ import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.IconedItem;
|
||||
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import org.jackhuang.hmcl.util.MappedObservableList;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static javafx.collections.FXCollections.singletonObservableList;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LeftPaneController {
|
||||
private final AdvancedListBox leftPane;
|
||||
private final VBox profilePane = new VBox();
|
||||
private final VBox accountPane = new VBox();
|
||||
private final IconedItem launcherSettingsItem;
|
||||
private final VersionListItem missingAccountItem = new VersionListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
private final HashMap<Account, VersionListItem> items = new HashMap<>();
|
||||
|
||||
private ListProperty<RipplerContainer> accountItems = new SimpleListProperty<>();
|
||||
private ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>() {
|
||||
{
|
||||
accountItems.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Account selected = get();
|
||||
accountItems.forEach(item -> item.setSelected(
|
||||
getAccountFromItem(item)
|
||||
.map(it -> it == selected)
|
||||
.orElse(false)));
|
||||
}
|
||||
};
|
||||
|
||||
public LeftPaneController(AdvancedListBox leftPane) {
|
||||
this.leftPane = leftPane;
|
||||
@@ -90,30 +114,91 @@ public final class LeftPaneController {
|
||||
})))
|
||||
.add(profilePane);
|
||||
|
||||
EventBus.EVENT_BUS.channel(AccountAddedEvent.class).register(this::onAccountAdd);
|
||||
EventBus.EVENT_BUS.channel(AccountLoadingEvent.class).register(this::onAccountsLoading);
|
||||
// ==== Accounts ====
|
||||
// Missing account item
|
||||
VersionListItem missingAccountItem = new VersionListItem(i18n("account.missing"), i18n("message.unknown"));
|
||||
RipplerContainer missingAccountRippler = new RipplerContainer(missingAccountItem);
|
||||
missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount());
|
||||
missingAccountRippler.setOnMouseClicked(e -> addNewAccount());
|
||||
|
||||
accountItems.bind(
|
||||
new When(Accounts.accountsProperty().emptyProperty())
|
||||
.then(singletonObservableList(missingAccountRippler))
|
||||
.otherwise(MappedObservableList.create(Accounts.getAccounts(), this::createAccountItem)));
|
||||
Bindings.bindContent(accountPane.getChildren(), accountItems);
|
||||
|
||||
selectedAccount.bindBidirectional(Accounts.selectedAccountProperty());
|
||||
// ====
|
||||
|
||||
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
|
||||
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(this::onRefreshedVersions);
|
||||
}
|
||||
|
||||
FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), this::onSelectedAccountChanged);
|
||||
onAccountsLoading();
|
||||
// ==== Accounts ====
|
||||
private Optional<Account> getAccountFromItem(RipplerContainer accountItem) {
|
||||
return Optional.ofNullable(accountItem.getProperties().get("account"))
|
||||
.map(Account.class::cast);
|
||||
}
|
||||
|
||||
private static String accountSubtitle(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return account.getUsername();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
private RipplerContainer createAccountItem(Account account) {
|
||||
VersionListItem item = new VersionListItem(account.getCharacter(), accountSubtitle(account));
|
||||
RipplerContainer rippler = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
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());
|
||||
});
|
||||
rippler.setOnMouseClicked(e -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY) {
|
||||
selectedAccount.set(account);
|
||||
}
|
||||
});
|
||||
rippler.getProperties().put("account", account);
|
||||
rippler.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
} else {
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
}
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
FXUtils.installTooltip(rippler, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
|
||||
}
|
||||
|
||||
// update skin
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
});
|
||||
}
|
||||
|
||||
return rippler;
|
||||
}
|
||||
|
||||
public void checkAccount() {
|
||||
if (Accounts.getAccounts().isEmpty())
|
||||
addNewAccount();
|
||||
}
|
||||
|
||||
private void addNewAccount() {
|
||||
Controllers.dialog(new AddAccountPane());
|
||||
}
|
||||
|
||||
private void onSelectedAccountChanged(Account newAccount) {
|
||||
Platform.runLater(() -> {
|
||||
for (Node node : accountPane.getChildren()) {
|
||||
if (node instanceof RipplerContainer && node.getProperties().get("account") instanceof Account) {
|
||||
boolean current = Objects.equals(node.getProperties().get("account"), newAccount);
|
||||
((RipplerContainer) node).setSelected(current);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// ====
|
||||
|
||||
private void onProfileChanged(ProfileChangedEvent event) {
|
||||
Profile profile = event.getProfile();
|
||||
@@ -131,11 +216,11 @@ public final class LeftPaneController {
|
||||
|
||||
private void onProfilesLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
for (Profile profile : Settings.INSTANCE.getProfiles()) {
|
||||
for (Profile profile : Settings.instance().getProfiles()) {
|
||||
VersionListItem item = new VersionListItem(Profiles.getProfileDisplayName(profile));
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> Controllers.getDecorator().showPage(new ProfilePage(profile)));
|
||||
ripplerContainer.setOnMouseClicked(e -> Settings.INSTANCE.setSelectedProfile(profile));
|
||||
ripplerContainer.setOnMouseClicked(e -> Settings.instance().setSelectedProfile(profile));
|
||||
ripplerContainer.getProperties().put("profile", profile.getName());
|
||||
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
list.add(ripplerContainer);
|
||||
@@ -143,70 +228,6 @@ public final class LeftPaneController {
|
||||
Platform.runLater(() -> profilePane.getChildren().setAll(list));
|
||||
}
|
||||
|
||||
private static String accountType(Account account) {
|
||||
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount) return account.getUsername();
|
||||
else throw new Error(i18n("account.methods.no_method") + ": " + account);
|
||||
}
|
||||
|
||||
private void onAccountAdd(AccountAddedEvent event) {
|
||||
Account account = event.getAccount();
|
||||
VersionListItem item = items.get(account);
|
||||
if (account instanceof YggdrasilAccount)
|
||||
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
|
||||
.subscribe(Schedulers.javafx(), () -> {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
});
|
||||
}
|
||||
|
||||
private void onAccountsLoading() {
|
||||
LinkedList<RipplerContainer> list = new LinkedList<>();
|
||||
items.clear();
|
||||
Account selectedAccount = Settings.INSTANCE.getSelectedAccount();
|
||||
for (Account account : Settings.INSTANCE.getAccounts()) {
|
||||
VersionListItem item = new VersionListItem(account.getCharacter(), accountType(account));
|
||||
items.put(account, item);
|
||||
RipplerContainer ripplerContainer = new RipplerContainer(item);
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
AccountPage accountPage = new AccountPage(account, item);
|
||||
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 -> {
|
||||
if (e.getButton() == MouseButton.PRIMARY)
|
||||
Settings.INSTANCE.setSelectedAccount(account);
|
||||
});
|
||||
ripplerContainer.getProperties().put("account", account);
|
||||
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty());
|
||||
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
|
||||
item.setImage(image, AccountHelper.getViewport(4));
|
||||
} else
|
||||
item.setImage(AccountHelper.getDefaultSkin(account.getUUID(), 4), AccountHelper.getViewport(4));
|
||||
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
FXUtils.installTooltip(ripplerContainer, 500, 5000, 0, new Tooltip(((AuthlibInjectorAccount) account).getServer().getName()));
|
||||
}
|
||||
|
||||
if (selectedAccount == account)
|
||||
ripplerContainer.setSelected(true);
|
||||
|
||||
list.add(ripplerContainer);
|
||||
}
|
||||
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty()) {
|
||||
RipplerContainer container = new RipplerContainer(missingAccountItem);
|
||||
missingAccountItem.setOnSettingsButtonClicked(e -> addNewAccount());
|
||||
container.setOnMouseClicked(e -> addNewAccount());
|
||||
list.add(container);
|
||||
}
|
||||
|
||||
Platform.runLater(() -> accountPane.getChildren().setAll(list));
|
||||
}
|
||||
|
||||
public void showUpdate() {
|
||||
launcherSettingsItem.setText(i18n("update.found"));
|
||||
launcherSettingsItem.setTextFill(Color.RED);
|
||||
@@ -247,9 +268,4 @@ public final class LeftPaneController {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void checkAccount() {
|
||||
if (Settings.INSTANCE.getAccounts().isEmpty())
|
||||
addNewAccount();
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -64,7 +65,7 @@ public final class LogWindow extends Stage {
|
||||
|
||||
public LogWindow() {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
setTitle(i18n("logwindow.title"));
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
@@ -176,12 +177,12 @@ public final class LogWindow extends Stage {
|
||||
|
||||
engine = webView.getEngine();
|
||||
engine.loadContent(Lang.ignoringException(() -> IOUtils.readFullyAsString(getClass().getResourceAsStream("/assets/log-window-content.html")))
|
||||
.replace("${FONT}", Settings.INSTANCE.getFont().getSize() + "px \"" + Settings.INSTANCE.getFont().getFamily() + "\""));
|
||||
.replace("${FONT}", Settings.instance().getFont().getSize() + "px \"" + Settings.instance().getFont().getFamily() + "\""));
|
||||
engine.getLoadWorker().stateProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
document = engine.getDocument();
|
||||
body = document.getElementsByTagName("body").item(0);
|
||||
engine.executeScript("limitedLogs=" + Settings.INSTANCE.getLogLines());
|
||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
||||
latch.countDown();
|
||||
onDone.fireEvent(new Event(LogWindow.this));
|
||||
}
|
||||
@@ -189,14 +190,14 @@ public final class LogWindow extends Stage {
|
||||
|
||||
boolean flag = false;
|
||||
for (String i : cboLines.getItems())
|
||||
if (Integer.toString(Settings.INSTANCE.getLogLines()).equals(i)) {
|
||||
if (Integer.toString(Settings.instance().getLogLines()).equals(i)) {
|
||||
cboLines.getSelectionModel().select(i);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
Settings.INSTANCE.setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + Settings.INSTANCE.getLogLines());
|
||||
Settings.instance().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + Settings.instance().getLogLines());
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
|
||||
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
@@ -48,6 +49,8 @@ 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.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -98,7 +101,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), i18n("install")));
|
||||
FXUtils.installTooltip(btnAdd, i18n("install"));
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersionsAsync().start());
|
||||
btnRefresh.setOnMouseClicked(e -> Settings.instance().getSelectedProfile().getRepository().refreshVersionsAsync().start());
|
||||
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
|
||||
}
|
||||
|
||||
@@ -132,13 +135,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
item.setVersionName(id);
|
||||
item.setOnLaunchButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.getLeftPaneController().checkAccount();
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null);
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, null);
|
||||
});
|
||||
item.setOnScriptButtonClicked(e -> {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else {
|
||||
FileChooser chooser = new FileChooser();
|
||||
@@ -150,7 +153,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
|
||||
File file = chooser.showSaveDialog(Controllers.getStage());
|
||||
if (file != null)
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, file);
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, file);
|
||||
}
|
||||
});
|
||||
item.setOnSettingsButtonClicked(e -> {
|
||||
@@ -214,10 +217,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
});
|
||||
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
|
||||
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||
if (Settings.INSTANCE.getSelectedAccount() == null)
|
||||
if (Accounts.getSelectedAccount() == null)
|
||||
Controllers.dialog(i18n("login.empty_username"));
|
||||
else
|
||||
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null);
|
||||
LauncherHelper.INSTANCE.launch(profile, Accounts.getSelectedAccount(), id, null);
|
||||
}
|
||||
});
|
||||
File iconFile = repository.getVersionIcon(id);
|
||||
@@ -233,6 +236,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
|
||||
private void loadVersions(HMCLGameRepository repository) {
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.sorted((a, b) -> VersionNumber.COMPARATOR.compare(VersionNumber.asVersion(a.getId()), VersionNumber.asVersion(b.getId())))
|
||||
.map(version -> buildNode(repository, version, () -> GameVersion.minecraftVersion(repository.getVersionJar(version.getId())).orElse("Unknown")))
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
|
||||
@@ -80,7 +80,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
if (profile != null) {
|
||||
Settings.INSTANCE.deleteProfile(profile);
|
||||
Settings.instance().deleteProfile(profile);
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
}
|
||||
@@ -99,10 +99,10 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
}
|
||||
Profile newProfile = new Profile(txtProfileName.getText(), new File(getLocation()));
|
||||
newProfile.setUseRelativePath(toggleUseRelativePath.isSelected());
|
||||
Settings.INSTANCE.putProfile(newProfile);
|
||||
Settings.instance().putProfile(newProfile);
|
||||
}
|
||||
|
||||
Settings.INSTANCE.onProfileLoading();
|
||||
Settings.instance().onProfileLoading();
|
||||
Controllers.navigate(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ import com.jfoenix.controls.*;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -28,7 +31,6 @@ import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
@@ -37,7 +39,6 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.construct.FileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.FontComboBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
@@ -45,12 +46,14 @@ import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("settings.launcher"));
|
||||
@@ -72,7 +75,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private FontComboBox cboFont;
|
||||
@FXML
|
||||
private FileItem fileCommonLocation;
|
||||
private MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||
@FXML
|
||||
private Label lblDisplay;
|
||||
@FXML
|
||||
@@ -84,13 +87,11 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private ScrollPane scroll;
|
||||
@FXML
|
||||
private MultiFileItem backgroundItem;
|
||||
private MultiFileItem<EnumBackgroundImage> backgroundItem;
|
||||
@FXML
|
||||
private StackPane themeColorPickerContainer;
|
||||
@FXML
|
||||
private JFXRadioButton chkNoProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkManualProxy;
|
||||
private JFXCheckBox chkEnableProxy;
|
||||
@FXML
|
||||
private JFXRadioButton chkProxyHttp;
|
||||
@FXML
|
||||
@@ -102,128 +103,125 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
@FXML
|
||||
private Pane proxyPane;
|
||||
|
||||
{
|
||||
private ObjectProperty<Proxy.Type> selectedProxyType;
|
||||
|
||||
public SettingsPage() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/setting.fxml");
|
||||
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
txtProxyHost.setText(Settings.INSTANCE.getProxyHost());
|
||||
txtProxyHost.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyHost(newValue));
|
||||
cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.instance().getDownloadProvider()));
|
||||
cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.instance().setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue())));
|
||||
|
||||
txtProxyPort.setText(Settings.INSTANCE.getProxyPort());
|
||||
txtProxyPort.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyPort(newValue));
|
||||
|
||||
txtProxyUsername.setText(Settings.INSTANCE.getProxyUser());
|
||||
txtProxyUsername.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyUser(newValue));
|
||||
|
||||
txtProxyPassword.setText(Settings.INSTANCE.getProxyPass());
|
||||
txtProxyPassword.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyPass(newValue));
|
||||
|
||||
cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.INSTANCE.getDownloadProvider()));
|
||||
cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue())));
|
||||
|
||||
cboFont.getSelectionModel().select(Settings.INSTANCE.getFont().getFamily());
|
||||
cboFont.getSelectionModel().select(Settings.instance().getFont().getFamily());
|
||||
cboFont.valueProperty().addListener((a, b, newValue) -> {
|
||||
Font font = Font.font(newValue, Settings.INSTANCE.getFont().getSize());
|
||||
Settings.INSTANCE.setFont(font);
|
||||
Font font = Font.font(newValue, Settings.instance().getFont().getSize());
|
||||
Settings.instance().setFont(font);
|
||||
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
|
||||
});
|
||||
|
||||
txtFontSize.setText(Double.toString(Settings.INSTANCE.getFont().getSize()));
|
||||
txtFontSize.setText(Double.toString(Settings.instance().getFont().getSize()));
|
||||
txtFontSize.getValidators().add(new Validator(it -> Lang.toDoubleOrNull(it) != null));
|
||||
txtFontSize.textProperty().addListener((a, b, newValue) -> {
|
||||
if (txtFontSize.validate()) {
|
||||
Font font = Font.font(Settings.INSTANCE.getFont().getFamily(), Double.parseDouble(newValue));
|
||||
Settings.INSTANCE.setFont(font);
|
||||
Font font = Font.font(Settings.instance().getFont().getFamily(), Double.parseDouble(newValue));
|
||||
Settings.instance().setFont(font);
|
||||
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
|
||||
}
|
||||
});
|
||||
|
||||
lblDisplay.setStyle("-fx-font: " + Settings.INSTANCE.getFont().getSize() + " \"" + Settings.INSTANCE.getFont().getFamily() + "\";");
|
||||
lblDisplay.setStyle("-fx-font: " + Settings.instance().getFont().getSize() + " \"" + Settings.instance().getFont().getFamily() + "\";");
|
||||
|
||||
ObservableList<Label> list = FXCollections.observableArrayList();
|
||||
for (Locales.SupportedLocale locale : Locales.LOCALES)
|
||||
list.add(new Label(locale.getName(Settings.INSTANCE.getLocale().getResourceBundle())));
|
||||
list.add(new Label(locale.getName(config().getLocalization().getResourceBundle())));
|
||||
|
||||
cboLanguage.setItems(list);
|
||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(Settings.INSTANCE.getLocale()));
|
||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setLocale(Locales.getLocale(newValue.intValue())));
|
||||
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(config().getLocalization()));
|
||||
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> config().setLocalization(Locales.getLocale(newValue.intValue())));
|
||||
|
||||
// ==== Proxy ====
|
||||
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
|
||||
txtProxyPort.textProperty().bindBidirectional(config().proxyPortProperty());
|
||||
txtProxyUsername.textProperty().bindBidirectional(config().proxyUserProperty());
|
||||
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
|
||||
|
||||
proxyPane.disableProperty().bind(chkEnableProxy.selectedProperty().not());
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
chkEnableProxy.selectedProperty().bindBidirectional(config().hasProxyProperty());
|
||||
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
|
||||
|
||||
selectedProxyType = new SimpleObjectProperty<Proxy.Type>(Proxy.Type.HTTP) {
|
||||
{
|
||||
invalidated();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Proxy.Type type = Objects.requireNonNull(get());
|
||||
if (type == Proxy.Type.DIRECT) {
|
||||
set(Proxy.Type.HTTP); // HTTP by default
|
||||
} else {
|
||||
chkProxyHttp.setSelected(type == Proxy.Type.HTTP);
|
||||
chkProxySocks.setSelected(type == Proxy.Type.SOCKS);
|
||||
}
|
||||
}
|
||||
};
|
||||
selectedProxyType.bindBidirectional(config().proxyTypeProperty());
|
||||
|
||||
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
|
||||
chkProxyHttp.setUserData(Proxy.Type.HTTP);
|
||||
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
|
||||
chkProxySocks.setUserData(Proxy.Type.SOCKS);
|
||||
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
|
||||
proxyConfigurationGroup.getToggles().forEach(
|
||||
toggle -> toggle.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
selectedProxyType.set((Proxy.Type) toggle.getUserData());
|
||||
}
|
||||
}));
|
||||
// ====
|
||||
|
||||
for (Toggle toggle : proxyConfigurationGroup.getToggles())
|
||||
if (toggle.getUserData() == Settings.INSTANCE.getProxyType())
|
||||
toggle.setSelected(true);
|
||||
|
||||
ToggleGroup hasProxyGroup = new ToggleGroup();
|
||||
chkNoProxy.setToggleGroup(hasProxyGroup);
|
||||
chkManualProxy.setToggleGroup(hasProxyGroup);
|
||||
if (!Settings.INSTANCE.hasProxy())
|
||||
chkNoProxy.setSelected(true);
|
||||
else
|
||||
chkManualProxy.setSelected(true);
|
||||
proxyPane.disableProperty().bind(chkNoProxy.selectedProperty());
|
||||
|
||||
hasProxyGroup.selectedToggleProperty().addListener((a, b, newValue) ->
|
||||
Settings.INSTANCE.setHasProxy(newValue != chkNoProxy));
|
||||
|
||||
proxyConfigurationGroup.selectedToggleProperty().addListener((a, b, newValue) ->
|
||||
Settings.INSTANCE.setProxyType((Proxy.Type) newValue.getUserData()));
|
||||
|
||||
chkProxyAuthentication.setSelected(Settings.INSTANCE.hasProxyAuth());
|
||||
chkProxyAuthentication.selectedProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setHasProxyAuth(newValue));
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
fileCommonLocation.pathProperty().bindBidirectional(Settings.INSTANCE.commonPathProperty());
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
fileCommonLocation.createChildren(i18n("launcher.common_directory.disabled"), EnumCommonDirectory.DISABLED),
|
||||
fileCommonLocation.createChildren(i18n("launcher.common_directory.default"), EnumCommonDirectory.DEFAULT)
|
||||
), EnumCommonDirectory.CUSTOM);
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
fileCommonLocation.customTextProperty().bindBidirectional(config().commonDirectoryProperty());
|
||||
fileCommonLocation.subtitleProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(i18n("launcher.common_directory.disabled")),
|
||||
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
|
||||
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
checkUpdate();
|
||||
|
||||
// background
|
||||
// ==== Background ====
|
||||
backgroundItem.loadChildren(Collections.singletonList(
|
||||
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
));
|
||||
), EnumBackgroundImage.CUSTOM);
|
||||
backgroundItem.customTextProperty().bindBidirectional(config().backgroundImageProperty());
|
||||
backgroundItem.selectedDataProperty().bindBidirectional(config().backgroundImageTypeProperty());
|
||||
backgroundItem.subtitleProperty().bind(
|
||||
new When(backgroundItem.selectedDataProperty().isEqualTo(EnumBackgroundImage.DEFAULT))
|
||||
.then(i18n("launcher.background.default"))
|
||||
.otherwise(config().backgroundImageProperty()));
|
||||
// ====
|
||||
|
||||
FXUtils.bindString(backgroundItem.getTxtCustom(), Settings.INSTANCE.backgroundImageProperty());
|
||||
|
||||
backgroundItem.setCustomUserData(EnumBackgroundImage.CUSTOM);
|
||||
backgroundItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Settings.INSTANCE.getBackgroundImageType()).findFirst().ifPresent(it -> it.setSelected(true));
|
||||
|
||||
Settings.INSTANCE.backgroundImageProperty().setChangedListener(it -> initBackgroundItemSubtitle());
|
||||
Settings.INSTANCE.backgroundImageTypeProperty().setChangedListener(it -> initBackgroundItemSubtitle());
|
||||
initBackgroundItemSubtitle();
|
||||
|
||||
backgroundItem.setToggleSelectedListener(newValue ->
|
||||
Settings.INSTANCE.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData()));
|
||||
|
||||
// theme
|
||||
JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null);
|
||||
// ==== Theme ====
|
||||
JFXColorPicker picker = new JFXColorPicker(Color.web(config().getTheme().getColor()), null);
|
||||
picker.setCustomColorText(i18n("color.custom"));
|
||||
picker.setRecentColorsText(i18n("color.recent"));
|
||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||
picker.setOnAction(e -> {
|
||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
||||
Settings.INSTANCE.setTheme(theme);
|
||||
config().setTheme(theme);
|
||||
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets());
|
||||
});
|
||||
themeColorPickerContainer.getChildren().setAll(picker);
|
||||
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
|
||||
}
|
||||
|
||||
private void initBackgroundItemSubtitle() {
|
||||
switch (Settings.INSTANCE.getBackgroundImageType()) {
|
||||
case DEFAULT:
|
||||
backgroundItem.setSubtitle(i18n("launcher.background.default"));
|
||||
break;
|
||||
case CUSTOM:
|
||||
backgroundItem.setSubtitle(Settings.INSTANCE.getBackgroundImage());
|
||||
break;
|
||||
}
|
||||
// ====
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
|
||||
@@ -71,7 +71,7 @@ public final class VersionItem extends StackPane {
|
||||
private void initializeComponents() {
|
||||
setPickOnBounds(false);
|
||||
FXUtils.setLimitWidth(this, 160);
|
||||
FXUtils.setLimitHeight(this, 156);
|
||||
FXUtils.setLimitHeight(this, 149);
|
||||
|
||||
content = new VBox();
|
||||
{
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import com.jfoenix.controls.JFXToggleButton;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
@@ -43,6 +44,7 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
@@ -70,11 +72,10 @@ public final class VersionSettingsController {
|
||||
@FXML private JFXCheckBox chkFullscreen;
|
||||
@FXML private Label lblPhysicalMemory;
|
||||
@FXML private JFXToggleButton chkNoJVMArgs;
|
||||
@FXML private JFXToggleButton chkNoCommon;
|
||||
@FXML private JFXToggleButton chkNoGameCheck;
|
||||
@FXML private MultiFileItem globalItem;
|
||||
@FXML private MultiFileItem javaItem;
|
||||
@FXML private MultiFileItem gameDirItem;
|
||||
@FXML private MultiFileItem<Boolean> globalItem;
|
||||
@FXML private MultiFileItem<JavaVersion> javaItem;
|
||||
@FXML private MultiFileItem<EnumGameDirectory> gameDirItem;
|
||||
@FXML private JFXToggleButton chkShowLogs;
|
||||
@FXML private ImagePickerItem iconPickerItem;
|
||||
|
||||
@@ -94,9 +95,12 @@ public final class VersionSettingsController {
|
||||
initializeSelectedJava();
|
||||
});
|
||||
|
||||
javaItem.setSelectedData(null);
|
||||
javaItem.setFallbackData(JavaVersion.fromCurrentEnvironment());
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
|
||||
javaItem.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe"));
|
||||
|
||||
gameDirItem.setCustomUserData(EnumGameDirectory.CUSTOM);
|
||||
gameDirItem.loadChildren(Arrays.asList(
|
||||
gameDirItem.createChildren(i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
|
||||
gameDirItem.createChildren(i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
|
||||
@@ -132,15 +136,19 @@ public final class VersionSettingsController {
|
||||
FXUtils.unbindString(txtServerIP, lastVersionSetting.serverIpProperty());
|
||||
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
|
||||
FXUtils.unbindBoolean(chkNoGameCheck, lastVersionSetting.notCheckGameProperty());
|
||||
FXUtils.unbindBoolean(chkNoCommon, lastVersionSetting.noCommonProperty());
|
||||
FXUtils.unbindBoolean(chkNoJVMArgs, lastVersionSetting.noJVMArgsProperty());
|
||||
FXUtils.unbindBoolean(chkShowLogs, lastVersionSetting.showLogsProperty());
|
||||
FXUtils.unbindEnum(cboLauncherVisibility);
|
||||
|
||||
globalItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.usesGlobalProperty());
|
||||
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().unbind();
|
||||
}
|
||||
|
||||
// unbind data fields
|
||||
globalItem.setToggleSelectedListener(null);
|
||||
javaItem.setToggleSelectedListener(null);
|
||||
gameDirItem.setToggleSelectedListener(null);
|
||||
|
||||
// bind new data fields
|
||||
FXUtils.bindInt(txtWidth, versionSetting.widthProperty());
|
||||
@@ -156,18 +164,15 @@ public final class VersionSettingsController {
|
||||
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
|
||||
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
|
||||
FXUtils.bindBoolean(chkNoGameCheck, versionSetting.notCheckGameProperty());
|
||||
FXUtils.bindBoolean(chkNoCommon, versionSetting.noCommonProperty());
|
||||
FXUtils.bindBoolean(chkNoJVMArgs, versionSetting.noJVMArgsProperty());
|
||||
FXUtils.bindBoolean(chkShowLogs, versionSetting.showLogsProperty());
|
||||
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
|
||||
|
||||
javaItem.setToggleSelectedListener(newValue -> {
|
||||
if (javaItem.isCustomToggle(newValue)) {
|
||||
versionSetting.setJava("Custom");
|
||||
versionSetting.setDefaultJavaPath(null);
|
||||
versionSetting.setUsesCustomJavaDir();
|
||||
} else {
|
||||
JavaVersion java = (JavaVersion) newValue.getUserData();
|
||||
versionSetting.setJava(java.getVersion());
|
||||
versionSetting.setDefaultJavaPath(java.getBinary().toString());
|
||||
versionSetting.setJavaVersion((JavaVersion) newValue.getUserData());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -175,13 +180,13 @@ public final class VersionSettingsController {
|
||||
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
|
||||
initJavaSubtitle(versionSetting);
|
||||
|
||||
if (versionSetting.isUsesGlobal())
|
||||
globalItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Boolean.TRUE).findFirst().ifPresent(it -> it.setSelected(true));
|
||||
else
|
||||
globalItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Boolean.FALSE).findFirst().ifPresent(it -> it.setSelected(true));
|
||||
globalItem.selectedDataProperty().bindBidirectional(versionSetting.usesGlobalProperty());
|
||||
globalItem.subtitleProperty().bind(Bindings.createStringBinding(() -> i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special"),
|
||||
versionSetting.usesGlobalProperty()));
|
||||
globalItem.setToggleSelectedListener(newValue -> {
|
||||
// do not call versionSettings.setUsesGlobal(true/false)
|
||||
// because versionSettings can be the global one.
|
||||
// global versionSettings.usesGlobal is always true.
|
||||
if ((Boolean) newValue.getUserData())
|
||||
profile.globalizeVersionSetting(versionId);
|
||||
else
|
||||
@@ -190,19 +195,9 @@ public final class VersionSettingsController {
|
||||
Platform.runLater(() -> loadVersionSetting(profile, versionId));
|
||||
});
|
||||
|
||||
versionSetting.usesGlobalProperty().setChangedListenerAndOperate(it ->
|
||||
globalItem.setSubtitle(i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special")));
|
||||
|
||||
gameDirItem.getGroup().getToggles().stream()
|
||||
.filter(it -> it.getUserData() == versionSetting.getGameDirType())
|
||||
.findFirst().ifPresent(toggle -> toggle.setSelected(true));
|
||||
|
||||
gameDirItem.setCustomUserData(EnumGameDirectory.CUSTOM);
|
||||
gameDirItem.setToggleSelectedListener(newValue -> versionSetting.setGameDirType((EnumGameDirectory) newValue.getUserData()));
|
||||
|
||||
versionSetting.gameDirProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
|
||||
versionSetting.gameDirTypeProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
|
||||
initGameDirSubtitle(versionSetting);
|
||||
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(),
|
||||
versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
@@ -217,30 +212,14 @@ public final class VersionSettingsController {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Toggle> toggles = javaItem.getGroup().getToggles();
|
||||
if ("Custom".equals(lastVersionSetting.getJava())) {
|
||||
toggles.stream()
|
||||
.filter(javaItem::isCustomToggle)
|
||||
.findFirst()
|
||||
.get().setSelected(true);
|
||||
if (lastVersionSetting.isUsesCustomJavaDir()) {
|
||||
javaItem.setSelectedData(null);
|
||||
} else {
|
||||
JavaVersion selectedJava;
|
||||
try {
|
||||
selectedJava = lastVersionSetting.getJavaVersion();
|
||||
javaItem.setSelectedData(lastVersionSetting.getJavaVersion());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
toggles.stream()
|
||||
.filter(it -> it.getUserData() == selectedJava)
|
||||
.findFirst()
|
||||
.orElseGet( // fallback to select current java
|
||||
() -> toggles.stream()
|
||||
.filter(it -> it.getUserData() == JavaVersion.fromCurrentEnvironment())
|
||||
.findFirst()
|
||||
.get())
|
||||
.setSelected(true);
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,10 +230,6 @@ public final class VersionSettingsController {
|
||||
.map(JavaVersion::getBinary).map(File::getAbsolutePath).orElse("Invalid Java Directory"))));
|
||||
}
|
||||
|
||||
private void initGameDirSubtitle(VersionSetting versionSetting) {
|
||||
gameDirItem.setSubtitle(profile.getRepository().getRunDirectory(versionId).getAbsolutePath());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onExploreIcon() {
|
||||
FileChooser chooser = new FileChooser();
|
||||
|
||||
@@ -21,14 +21,15 @@ import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class WebStage extends Stage {
|
||||
private final WebView webView = new WebView();
|
||||
|
||||
public WebStage() {
|
||||
setScene(new Scene(webView, 800, 480));
|
||||
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ public class FileItem extends BorderPane {
|
||||
Path given = Paths.get(path).toAbsolutePath();
|
||||
if (isConvertToRelativePath()) {
|
||||
try {
|
||||
return Paths.get(".").toAbsolutePath().relativize(given).normalize().toString();
|
||||
return Paths.get(".").normalize().toAbsolutePath().relativize(given).normalize().toString();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// the given path can't be relativized against current path
|
||||
}
|
||||
|
||||
@@ -21,10 +21,7 @@ import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -46,12 +43,15 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MultiFileItem extends ComponentList {
|
||||
public class MultiFileItem<T> extends ComponentList {
|
||||
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
|
||||
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
|
||||
private final BooleanProperty directory = new SimpleBooleanProperty(this, "directory", false);
|
||||
private final ObjectProperty<T> selectedData = new SimpleObjectProperty<>(this, "selectedData");
|
||||
private final ObjectProperty<T> fallbackData = new SimpleObjectProperty<>(this, "fallbackData");
|
||||
private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
|
||||
|
||||
private final ToggleGroup group = new ToggleGroup();
|
||||
@@ -64,6 +64,7 @@ public class MultiFileItem extends ComponentList {
|
||||
|
||||
private Consumer<Toggle> toggleSelectedListener;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public MultiFileItem(@NamedArg(value = "hasCustom", defaultValue = "true") boolean hasCustom) {
|
||||
this.hasCustom = hasCustom;
|
||||
|
||||
@@ -110,6 +111,20 @@ public class MultiFileItem extends ComponentList {
|
||||
group.selectedToggleProperty().addListener((a, b, newValue) -> {
|
||||
if (toggleSelectedListener != null)
|
||||
toggleSelectedListener.accept(newValue);
|
||||
|
||||
selectedData.set((T) newValue.getUserData());
|
||||
});
|
||||
selectedData.addListener((a, b, newValue) -> {
|
||||
Optional<Toggle> selecting = group.getToggles().stream()
|
||||
.filter(it -> it.getUserData() == newValue)
|
||||
.findFirst();
|
||||
if (!selecting.isPresent()) {
|
||||
selecting = group.getToggles().stream()
|
||||
.filter(it -> it.getUserData() == getFallbackData())
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
selecting.ifPresent(toggle -> toggle.setSelected(true));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,11 +132,11 @@ public class MultiFileItem extends ComponentList {
|
||||
return createChildren(title, null);
|
||||
}
|
||||
|
||||
public Node createChildren(String title, Object userData) {
|
||||
public Node createChildren(String title, T userData) {
|
||||
return createChildren(title, "", userData);
|
||||
}
|
||||
|
||||
public Node createChildren(String title, String subtitle, Object userData) {
|
||||
public Node createChildren(String title, String subtitle, T userData) {
|
||||
BorderPane pane = new BorderPane();
|
||||
pane.setStyle("-fx-padding: 3;");
|
||||
FXUtils.setLimitHeight(pane, 20);
|
||||
@@ -146,12 +161,9 @@ public class MultiFileItem extends ComponentList {
|
||||
pane.getChildren().add(custom);
|
||||
}
|
||||
|
||||
public void onExploreJavaDir() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(i18n(getChooserTitle()));
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null)
|
||||
txtCustom.setText(selectedDir.getAbsolutePath());
|
||||
public void loadChildren(Collection<Node> list, T customUserData) {
|
||||
loadChildren(list);
|
||||
setCustomUserData(customUserData);
|
||||
}
|
||||
|
||||
public ToggleGroup getGroup() {
|
||||
@@ -182,7 +194,7 @@ public class MultiFileItem extends ComponentList {
|
||||
this.chooserTitle.set(chooserTitle);
|
||||
}
|
||||
|
||||
public void setCustomUserData(Object userData) {
|
||||
public void setCustomUserData(T userData) {
|
||||
radioCustom.setUserData(userData);
|
||||
}
|
||||
|
||||
@@ -225,4 +237,28 @@ public class MultiFileItem extends ComponentList {
|
||||
public ObservableList<FileChooser.ExtensionFilter> getExtensionFilters() {
|
||||
return extensionFilters;
|
||||
}
|
||||
|
||||
public T getSelectedData() {
|
||||
return selectedData.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<T> selectedDataProperty() {
|
||||
return selectedData;
|
||||
}
|
||||
|
||||
public void setSelectedData(T selectedData) {
|
||||
this.selectedData.set(selectedData);
|
||||
}
|
||||
|
||||
public T getFallbackData() {
|
||||
return fallbackData.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<T> fallbackDataProperty() {
|
||||
return fallbackData;
|
||||
}
|
||||
|
||||
public void setFallbackData(T fallbackData) {
|
||||
this.fallbackData.set(fallbackData);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public final class DownloadWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public void start(Map<String, Object> settings) {
|
||||
profile = Settings.INSTANCE.getSelectedProfile();
|
||||
profile = Settings.instance().getSelectedProfile();
|
||||
settings.put(PROFILE, profile);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.download;
|
||||
import javafx.scene.Node;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -100,9 +101,10 @@ public final class InstallerWizardProvider implements WizardProvider {
|
||||
|
||||
@Override
|
||||
public Node createPage(WizardController controller, int step, Map<String, Object> settings) {
|
||||
DownloadProvider provider = profile.getDependency().getDownloadProvider();
|
||||
switch (step) {
|
||||
case 0:
|
||||
return new AdditionalInstallersPage(this, controller, profile.getRepository(), BMCLAPIDownloadProvider.INSTANCE);
|
||||
return new AdditionalInstallersPage(this, controller, profile.getRepository(), provider);
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
@@ -38,6 +37,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public final class ExportWizardProvider implements WizardProvider {
|
||||
private final Profile profile;
|
||||
private final String version;
|
||||
@@ -81,20 +82,13 @@ public final class ExportWizardProvider implements WizardProvider {
|
||||
boolean flag = true;
|
||||
|
||||
try (ZipEngine zip = new ZipEngine(modpackFile)) {
|
||||
Config config = Settings.INSTANCE.getRawConfig();
|
||||
|
||||
config.hasProxy.set(false);
|
||||
config.selectedProfile.set("");
|
||||
config.commonDirectory.set(null);
|
||||
config.fontFamily.set("Consolas");
|
||||
config.fontSize.set(12);
|
||||
config.localization.set(null);
|
||||
config.accounts.clear();
|
||||
config.selectedAccount.set("");
|
||||
config.logLines.set(100);
|
||||
config.configurations.clear();
|
||||
|
||||
zip.putTextFile(config.toJson(), ConfigHolder.CONFIG_FILENAME);
|
||||
Config exported = new Config();
|
||||
exported.setBackgroundImageType(config().getBackgroundImageType());
|
||||
exported.setBackgroundImage(config().getBackgroundImage());
|
||||
exported.setTheme(config().getTheme());
|
||||
exported.setDownloadType(config().getDownloadType());
|
||||
exported.getAuthlibInjectorServers().setAll(config().getAuthlibInjectorServers());
|
||||
zip.putTextFile(exported.toJson(), ConfigHolder.CONFIG_FILENAME);
|
||||
zip.putFile(tempModpack, "modpack.zip");
|
||||
|
||||
File bg = new File("bg").getAbsoluteFile();
|
||||
|
||||
@@ -28,7 +28,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.wizard.WizardController;
|
||||
@@ -67,7 +67,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
|
||||
txtModpackName.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackAuthor.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackVersion.textProperty().addListener(e -> checkValidation());
|
||||
txtModpackAuthor.setText(Optional.ofNullable(Settings.INSTANCE.getSelectedAccount()).map(Account::getUsername).orElse(""));
|
||||
txtModpackAuthor.setText(Optional.ofNullable(Accounts.getSelectedAccount()).map(Account::getUsername).orElse(""));
|
||||
lblVersionName.setText(version);
|
||||
|
||||
List<File> launcherJar = Launcher.getCurrentJarFiles();
|
||||
|
||||
@@ -185,7 +185,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, new IntegrityCheck("SHA-1", hash)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -235,7 +235,7 @@ public class AppDataUpgrader extends IUpgrader {
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependents() {
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, new IntegrityCheck("SHA-1", hash)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -23,24 +23,25 @@ import java.util.MissingResourceException;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
public final class I18n {
|
||||
|
||||
private I18n() {}
|
||||
|
||||
private static ResourceBundle RESOURCE_BUNDLE = null;
|
||||
|
||||
static {
|
||||
private static SupportedLocale getCurrentLocale() {
|
||||
try {
|
||||
RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.SEVERE, "Settings cannot be initialized", e);
|
||||
return ConfigHolder.config().getLocalization();
|
||||
} catch (IllegalStateException e) {
|
||||
// e is thrown by ConfigHolder.config(), indicating the config hasn't been loaded
|
||||
// fallback to use default locale
|
||||
return Locales.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
public static ResourceBundle getResourceBundle() {
|
||||
return RESOURCE_BUNDLE == null ? Locales.DEFAULT.getResourceBundle() : RESOURCE_BUNDLE;
|
||||
return getCurrentLocale().getResourceBundle();
|
||||
}
|
||||
|
||||
public static String i18n(String key, Object... formatArgs) {
|
||||
|
||||
@@ -19,6 +19,10 @@ package org.jackhuang.hmcl.util.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
@@ -107,5 +111,17 @@ public final class Locales {
|
||||
if (name == null) return resourceBundle.getString("lang");
|
||||
else return newResourceBundle.getString(name);
|
||||
}
|
||||
|
||||
public static class TypeAdapter extends com.google.gson.TypeAdapter<SupportedLocale> {
|
||||
@Override
|
||||
public void write(JsonWriter out, SupportedLocale value) throws IOException {
|
||||
out.value(getNameByLocale(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public SupportedLocale read(JsonReader in) throws IOException {
|
||||
return getLocaleByName(in.nextString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
<JFXSpinner fx:id="spinner" styleClass="first-spinner" />
|
||||
<StackPane fx:id="contentPane">
|
||||
<ScrollPane fitToWidth="true" fx:id="scrollPane" hbarPolicy="NEVER">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="160">
|
||||
<JFXMasonryPane fx:id="masonryPane" HSpacing="3" VSpacing="3" cellWidth="182" cellHeight="153">
|
||||
</JFXMasonryPane>
|
||||
</ScrollPane>
|
||||
<VBox style="-fx-padding: 15;" spacing="15" pickOnBounds="false" alignment="BOTTOM_RIGHT">
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
<FileItem fx:id="fileCommonLocation" name="%launcher.common_directory" title="%launcher.common_directory.choose" tooltip="%settings.launcher.common_path.tooltip" />
|
||||
<MultiFileItem fx:id="fileCommonLocation" title="%launcher.common_directory" directory="true" chooserTitle="%launcher.common_directory.choose" hasSubtitle="true" customText="%settings.custom" />
|
||||
|
||||
<MultiFileItem fx:id="backgroundItem" title="%launcher.background" chooserTitle="%launcher.background.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" />
|
||||
@@ -55,8 +55,7 @@
|
||||
|
||||
<ComponentList title="%settings.launcher.proxy"> <!-- proxy -->
|
||||
<VBox spacing="10">
|
||||
<JFXRadioButton fx:id="chkNoProxy" text="%settings.launcher.proxy.no_proxy" />
|
||||
<JFXRadioButton fx:id="chkManualProxy" text="%settings.launcher.proxy.has_proxy" />
|
||||
<JFXCheckBox fx:id="chkEnableProxy" text="%settings.launcher.proxy.enable" />
|
||||
<VBox fx:id="proxyPane" style="-fx-padding: 0 0 0 30;">
|
||||
<HBox>
|
||||
<JFXRadioButton fx:id="chkProxyHttp" text="%settings.launcher.proxy.http" />
|
||||
|
||||
@@ -139,14 +139,6 @@
|
||||
<JFXToggleButton fx:id="chkNoJVMArgs" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<Label text="%settings.advanced.no_common"/>
|
||||
</left>
|
||||
<right>
|
||||
<JFXToggleButton fx:id="chkNoCommon" size="7" FXUtils.limitHeight="10" />
|
||||
</right>
|
||||
</BorderPane>
|
||||
<BorderPane>
|
||||
<left>
|
||||
<Label text="%settings.advanced.dont_check_game_completeness"/>
|
||||
|
||||
@@ -35,12 +35,12 @@ account.choose=Choose a character
|
||||
account.create=Create a new account
|
||||
account.email=Email
|
||||
account.failed.connect_authentication_server=Cannot connect to the authentication server. Check your network.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
account.failed.invalid_credentials=Incorrect password, or you are forbidden to login temporarily.
|
||||
account.failed.invalid_password=Invalid password
|
||||
account.failed.invalid_token=Please log out and re-input your password to login.
|
||||
account.failed.no_character=No character in this account.
|
||||
account.failed.no_selected_server=No authentication server is selected.
|
||||
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
|
||||
account.failed.server_response_malformed=Invalid server response. Is this a valid authentication server?
|
||||
account.injector.add=Add an authentication server
|
||||
account.injector.manage=Manage authentication servers
|
||||
account.injector.manage.title=Authentication servers
|
||||
@@ -50,7 +50,6 @@ account.injector.server_url=Server URL
|
||||
account.injector.server_name=Server Name
|
||||
account.methods=Login Type
|
||||
account.methods.authlib_injector=authlib-injector
|
||||
account.methods.no_method=No login method
|
||||
account.methods.offline=Offline
|
||||
account.methods.yggdrasil=Mojang
|
||||
account.missing=None
|
||||
@@ -94,6 +93,7 @@ extension.sh=Bash shell
|
||||
|
||||
fatal.missing_javafx=JavaFX is missing.\nIf you are using Java 11 or later, please downgrade to Java 8 or 10.\nIf you are using OpenJDK, please ensure OpenJFX is included.
|
||||
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use HMCL, but HMCL will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause HMCL not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
|
||||
fatal.config_access_denied=The configuration at "%s" is not readable or writable.\nPlease grant HMCL read and write access to this file and its parent directory.
|
||||
|
||||
folder.config=Configs
|
||||
folder.coremod=Core Mod
|
||||
@@ -120,7 +120,7 @@ install.installer.liteloader=LiteLoader
|
||||
install.installer.not_installed=%s not Installed
|
||||
install.installer.optifine=OptiFine
|
||||
install.installer.version=%s Version
|
||||
install.modpack=Install a modpack (CurseForge supported)
|
||||
install.modpack=Install a modpack
|
||||
install.new_game=Install a New Game
|
||||
install.new_game.already_exists=This version has already been existing.
|
||||
install.new_game.current_game_version=Current Game Version
|
||||
@@ -140,6 +140,7 @@ launch.failed=Unable to launch
|
||||
launch.failed.cannot_create_jvm=Java virtual machine cannot be created. Java arguments may have problems. You can enable the no args mode in the settings.
|
||||
launch.failed.creating_process=Failed to create process, maybe your java path is wrong, please modify your java path.
|
||||
launch.failed.decompressing_natives=Unable to decompress native libraries.
|
||||
launch.failed.download_library=Unable to download library %s.
|
||||
launch.failed.executable_permission=Unable to add permission to the launch script
|
||||
launch.failed.exited_abnormally=Game exited abnormally, please visit the log, or ask someone for help.
|
||||
launch.state.dependencies=Dependencies
|
||||
@@ -155,6 +156,8 @@ launcher.background.choose=Choose background path.
|
||||
launcher.background.default=Default
|
||||
launcher.common_directory=Common Directory
|
||||
launcher.common_directory.choose=Choose common directory.
|
||||
launcher.common_directory.default=Default
|
||||
launcher.common_directory.disabled=Disabled
|
||||
launcher.contact=Contact Us
|
||||
launcher.crash=Hello Minecraft! Launcher has crashed!
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher has crashed! Your launcher is outdated. Update it!
|
||||
@@ -288,8 +291,7 @@ settings.launcher.language=Language
|
||||
settings.launcher.log_font=Log Font
|
||||
settings.launcher.proxy=Proxy
|
||||
settings.launcher.proxy.authentication=Proxy Authentication
|
||||
settings.launcher.proxy.no_proxy=No proxy
|
||||
settings.launcher.proxy.has_proxy=Proxy configuration
|
||||
settings.launcher.proxy.enable=Enable Proxy
|
||||
settings.launcher.proxy.host=Host
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=Password
|
||||
|
||||
@@ -35,12 +35,12 @@ account.choose=選擇一個角色
|
||||
account.create=新建賬戶
|
||||
account.email=郵箱
|
||||
account.failed.connect_authentication_server=無法連接認證服務器,可能是網絡問題
|
||||
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
|
||||
account.failed.invalid_credentials=您的用戶名或密碼錯誤,或者登錄次數過多被暫時禁止登錄,請稍後再試
|
||||
account.failed.invalid_password=無效的密碼
|
||||
account.failed.invalid_token=請嘗試登出並重新輸入密碼登錄
|
||||
account.failed.no_character=該帳號沒有角色
|
||||
account.failed.no_selected_server=未選擇認證服務器
|
||||
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
|
||||
account.failed.server_response_malformed=這個服務器不是合法的登錄驗證服務器
|
||||
account.injector.add=添加認證服務器
|
||||
account.injector.manage=管理認證服務器
|
||||
account.injector.manage.title=認證服務器
|
||||
@@ -50,7 +50,6 @@ account.injector.server_url=服務器地址
|
||||
account.injector.server_name=服務器名稱
|
||||
account.methods=登錄方式
|
||||
account.methods.authlib_injector=authlib-injector 登錄
|
||||
account.methods.no_method=沒有登入方式
|
||||
account.methods.offline=離線模式
|
||||
account.methods.yggdrasil=正版登錄
|
||||
account.missing=沒有賬戶
|
||||
@@ -94,6 +93,7 @@ extension.sh=Bash 腳本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=當前 Java 平台缺少 DST Root CA X3 證書。\n您依然可以使用 HMCL,但將無法連接到部分站點(如使用 Let's Encrypt 證書的站點),這可能會使 HMCL 無法正常工作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
|
||||
fatal.config_access_denied=HMCL 無法讀取或寫入位於 "%s" 的配置文件。\n請授予 HMCL 對該文件及其父目錄的讀寫權限。
|
||||
|
||||
folder.config=配置文件夾
|
||||
folder.coremod=核心MOD文件夾
|
||||
@@ -140,6 +140,7 @@ launch.failed=啓動失敗
|
||||
launch.failed.cannot_create_jvm=截獲到無法創建 Java 虛擬機,可能是 Java 參數有問題,可以在設置中開啓無參數模式啓動。
|
||||
launch.failed.creating_process=啓動失敗,在創建新進程時發生錯誤,可能是 Java 路徑錯誤。
|
||||
launch.failed.decompressing_natives=未能解壓遊戲本地庫。
|
||||
launch.failed.download_library=未能下载游戏依赖 %s.
|
||||
launch.failed.executable_permission=未能爲啓動文件添加執行權限。
|
||||
launch.failed.exited_abnormally=遊戲非正常退出,請查看日志文件,或聯繫他人尋求幫助。
|
||||
launch.state.dependencies=正在處理遊戲依賴
|
||||
@@ -155,6 +156,8 @@ launcher.background.choose=選擇背景路徑
|
||||
launcher.background.default=默認(自動檢索啓動器同目錄下的 background.png/jpg 及 bg 文件夾內的圖片)
|
||||
launcher.common_directory=公共文件夾
|
||||
launcher.common_directory.choose=選擇公共文件夾
|
||||
launcher.common_directory.default=默認
|
||||
launcher.common_directory.disabled=禁用
|
||||
launcher.contact=聯繫我們
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了無法處理的錯誤,請複製下列內容並通過 MCBBS、貼吧、GitHub 或 Minecraft Forum 反饋 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了無法處理的錯誤,已檢測到您的啓動器不是最新版本,請更新後再試!
|
||||
@@ -287,9 +290,8 @@ settings.launcher.download_source=下載源
|
||||
settings.launcher.language=語言
|
||||
settings.launcher.log_font=日誌字體
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=代理賬戶
|
||||
settings.launcher.proxy.no_proxy=直連
|
||||
settings.launcher.proxy.has_proxy=啓用驗證
|
||||
settings.launcher.proxy.authentication=身份驗證
|
||||
settings.launcher.proxy.enable=启用代理
|
||||
settings.launcher.proxy.host=主機
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密碼
|
||||
|
||||
@@ -35,12 +35,12 @@ account.choose=选择一个角色
|
||||
account.create=新建账户
|
||||
account.email=邮箱
|
||||
account.failed.connect_authentication_server=无法连接认证服务器,可能是网络问题
|
||||
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
|
||||
account.failed.invalid_credentials=您的用户名或密码错误,或者登录次数过多被暂时禁止登录,请稍后再试
|
||||
account.failed.invalid_password=无效的密码
|
||||
account.failed.invalid_token=请尝试登出并重新输入密码登录
|
||||
account.failed.no_character=该帐号没有角色
|
||||
account.failed.no_selected_server=未选择认证服务器
|
||||
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
|
||||
account.failed.server_response_malformed=这个服务器不是合法的登录验证服务器
|
||||
account.injector.add=添加认证服务器
|
||||
account.injector.manage=管理认证服务器
|
||||
account.injector.manage.title=认证服务器
|
||||
@@ -50,7 +50,6 @@ account.injector.server_url=服务器地址
|
||||
account.injector.server_name=服务器名称
|
||||
account.methods=登录方式
|
||||
account.methods.authlib_injector=authlib-injector 登录
|
||||
account.methods.no_method=没有登入方式
|
||||
account.methods.offline=离线模式
|
||||
account.methods.yggdrasil=正版登录
|
||||
account.missing=没有账户
|
||||
@@ -94,6 +93,7 @@ extension.sh=Bash 脚本
|
||||
|
||||
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
|
||||
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Let's Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
|
||||
fatal.config_access_denied=HMCL 无法读取或写入位于 "%s" 的配置文件。\n请授予 HMCL 对该文件及其父目录的读写权限。
|
||||
|
||||
folder.config=配置文件夹
|
||||
folder.coremod=核心MOD文件夹
|
||||
@@ -140,6 +140,7 @@ launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
launch.failed.decompressing_natives=未能解压游戏本地库。
|
||||
launch.failed.download_library=未能下载游戏依赖 %s.
|
||||
launch.failed.executable_permission=未能为启动文件添加执行权限。
|
||||
launch.failed.exited_abnormally=游戏非正常退出,请查看日志文件,或联系他人寻求帮助。
|
||||
launch.state.dependencies=正在处理游戏依赖
|
||||
@@ -153,8 +154,10 @@ launcher=启动器
|
||||
launcher.background=背景地址
|
||||
launcher.background.choose=选择背景路径
|
||||
launcher.background.default=默认(自动检索启动器同目录下的 background.png/jpg 及 bg 文件夹内的图片)
|
||||
launcher.common_directory=公共文件夹
|
||||
launcher.common_directory=公共文件夹(统一存放游戏资源文件,减少游戏体积)
|
||||
launcher.common_directory.choose=选择公共文件夹
|
||||
launcher.common_directory.default=默认(%AppData%/.minecraft 或者 ~/.minecraft)
|
||||
launcher.common_directory.disabled=禁用(总是使用游戏路径)
|
||||
launcher.contact=联系我们
|
||||
launcher.crash=Hello Minecraft! Launcher 遇到了无法处理的错误,请复制下列内容并通过 MCBBS、贴吧、GitHub 或 Minecraft Forum 反馈 bug。
|
||||
launcher.crash_out_dated=Hello Minecraft! Launcher 遇到了无法处理的错误,已检测到您的启动器不是最新版本,请更新后再试!
|
||||
@@ -287,9 +290,8 @@ settings.launcher.download_source=下载源
|
||||
settings.launcher.language=语言
|
||||
settings.launcher.log_font=日志字体
|
||||
settings.launcher.proxy=代理
|
||||
settings.launcher.proxy.authentication=代理账户
|
||||
settings.launcher.proxy.no_proxy=直连
|
||||
settings.launcher.proxy.has_proxy=启用验证
|
||||
settings.launcher.proxy.authentication=身份验证
|
||||
settings.launcher.proxy.enable=启用代理
|
||||
settings.launcher.proxy.host=主机
|
||||
settings.launcher.proxy.http=HTTP
|
||||
settings.launcher.proxy.password=密码
|
||||
|
||||
@@ -17,8 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import org.jackhuang.hmcl.util.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -27,7 +32,7 @@ import java.util.UUID;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public abstract class Account {
|
||||
public abstract class Account implements Observable {
|
||||
|
||||
/**
|
||||
* @return the name of the account who owns the character
|
||||
@@ -66,6 +71,26 @@ public abstract class Account {
|
||||
|
||||
public abstract void clearCache();
|
||||
|
||||
private ObservableHelper helper = new ObservableHelper(this);
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
helper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
helper.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the account has changed.
|
||||
* This method can be called from any thread.
|
||||
*/
|
||||
protected void invalidate() {
|
||||
Platform.runLater(helper::invalidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class AccountBuilder<T extends Account> {
|
||||
@@ -25,7 +24,6 @@ public final class AccountBuilder<T extends Account> {
|
||||
private String username;
|
||||
private String password = null;
|
||||
private Object additionalData = null;
|
||||
private Proxy proxy = Proxy.NO_PROXY;
|
||||
|
||||
public AccountBuilder() {
|
||||
}
|
||||
@@ -50,12 +48,7 @@ public final class AccountBuilder<T extends Account> {
|
||||
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);
|
||||
return factory.create(selector, Objects.requireNonNull(username), password, additionalData);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@@ -26,7 +25,7 @@ import java.util.Map;
|
||||
*/
|
||||
public abstract class AccountFactory<T extends Account> {
|
||||
|
||||
public abstract T create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException;
|
||||
public abstract T create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException;
|
||||
|
||||
public abstract T fromStorage(Map<Object, Object> storage, Proxy proxy);
|
||||
public abstract T fromStorage(Map<Object, Object> storage);
|
||||
}
|
||||
+13
@@ -31,6 +31,7 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
@@ -110,4 +111,16 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), server.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof AuthlibInjectorAccount))
|
||||
return false;
|
||||
AuthlibInjectorAccount another = (AuthlibInjectorAccount) obj;
|
||||
return super.equals(another) && server.equals(another.server);
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
@@ -45,24 +44,22 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
|
||||
public AuthlibInjectorAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
|
||||
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl())),
|
||||
server, authlibInjectorDownloader, username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthlibInjectorAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
public AuthlibInjectorAccount fromStorage(Map<Object, Object> storage) {
|
||||
Objects.requireNonNull(storage);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilSession session = YggdrasilSession.fromStorage(storage);
|
||||
|
||||
@@ -73,7 +70,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
|
||||
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
|
||||
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl())),
|
||||
server, authlibInjectorDownloader, username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -20,7 +20,6 @@ package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@@ -81,7 +80,7 @@ public class AuthlibInjectorDownloader {
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(), Proxy.NO_PROXY,
|
||||
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(),
|
||||
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||
.orElse(null))
|
||||
|
||||
@@ -105,4 +105,17 @@ public class OfflineAccount extends Account {
|
||||
.append("uuid", uuid)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return username.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof OfflineAccount))
|
||||
return false;
|
||||
OfflineAccount another = (OfflineAccount) obj;
|
||||
return username.equals(another.username);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -39,12 +38,12 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) {
|
||||
public OfflineAccount create(CharacterSelector selector, String username, String password, Object additionalData) {
|
||||
return new OfflineAccount(username, getUUIDFromUserName(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OfflineAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
public OfflineAccount fromStorage(Map<Object, Object> storage) {
|
||||
String username = tryCast(storage.get("username"), String.class)
|
||||
.orElseThrow(() -> new IllegalStateException("Offline account configuration malformed."));
|
||||
UUID uuid = tryCast(storage.get("uuid"), String.class)
|
||||
|
||||
@@ -114,6 +114,7 @@ public class YggdrasilAccount extends Account {
|
||||
}
|
||||
|
||||
this.characterUUID = this.session.getSelectedProfile().getId();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,4 +169,16 @@ public class YggdrasilAccount extends Account {
|
||||
return "YggdrasilAccount[username=" + getUsername() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return characterUUID.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof YggdrasilAccount))
|
||||
return false;
|
||||
YggdrasilAccount another = (YggdrasilAccount) obj;
|
||||
return characterUUID.equals(another.characterUUID);
|
||||
}
|
||||
}
|
||||
+4
-7
@@ -22,7 +22,6 @@ import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CharacterSelector;
|
||||
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@@ -42,28 +41,26 @@ public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData, Proxy proxy) throws AuthenticationException {
|
||||
public YggdrasilAccount create(CharacterSelector selector, String username, String password, Object additionalData) throws AuthenticationException {
|
||||
Objects.requireNonNull(selector);
|
||||
Objects.requireNonNull(username);
|
||||
Objects.requireNonNull(password);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider, proxy), username, null, null);
|
||||
YggdrasilAccount account = new YggdrasilAccount(new YggdrasilService(provider), username, null, null);
|
||||
account.logInWithPassword(password, selector);
|
||||
return account;
|
||||
}
|
||||
|
||||
@Override
|
||||
public YggdrasilAccount fromStorage(Map<Object, Object> storage, Proxy proxy) {
|
||||
public YggdrasilAccount fromStorage(Map<Object, Object> storage) {
|
||||
Objects.requireNonNull(storage);
|
||||
Objects.requireNonNull(proxy);
|
||||
|
||||
YggdrasilSession session = 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);
|
||||
return new YggdrasilAccount(new YggdrasilService(provider), username, session.getSelectedProfile().getId(), session);
|
||||
}
|
||||
|
||||
public static String randomToken() {
|
||||
|
||||
@@ -9,7 +9,6 @@ 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.*;
|
||||
|
||||
@@ -21,15 +20,9 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
public class YggdrasilService {
|
||||
|
||||
private final YggdrasilProvider provider;
|
||||
private final Proxy proxy;
|
||||
|
||||
public YggdrasilService(YggdrasilProvider provider) {
|
||||
this(provider, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public YggdrasilService(YggdrasilProvider provider, Proxy proxy) {
|
||||
this.provider = provider;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public YggdrasilSession authenticate(String username, String password, String clientToken) throws AuthenticationException {
|
||||
@@ -155,7 +148,7 @@ public class YggdrasilService {
|
||||
private String request(URL url, Object payload) throws AuthenticationException {
|
||||
try {
|
||||
if (payload == null)
|
||||
return NetworkUtils.doGet(url, proxy);
|
||||
return NetworkUtils.doGet(url);
|
||||
else
|
||||
return NetworkUtils.doPost(url, payload instanceof String ? (String) payload : GSON.toJson(payload), "application/json");
|
||||
} catch (IOException e) {
|
||||
|
||||
@@ -29,8 +29,6 @@ import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.ParallelTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* Note: This class has no state.
|
||||
*
|
||||
@@ -40,16 +38,10 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
|
||||
private final DefaultGameRepository repository;
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final Proxy proxy;
|
||||
|
||||
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider) {
|
||||
this(repository, downloadProvider, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public DefaultDependencyManager(DefaultGameRepository repository, DownloadProvider downloadProvider, Proxy proxy) {
|
||||
this.repository = repository;
|
||||
this.downloadProvider = downloadProvider;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,11 +54,6 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
return downloadProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Proxy getProxy() {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameBuilder gameBuilder() {
|
||||
return new DefaultGameBuilder(this);
|
||||
|
||||
@@ -21,8 +21,6 @@ import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.net.Proxy;
|
||||
|
||||
/**
|
||||
* Do everything that will connect to Internet.
|
||||
* Downloading Minecraft files.
|
||||
@@ -36,11 +34,6 @@ public interface DependencyManager {
|
||||
*/
|
||||
GameRepository getGameRepository();
|
||||
|
||||
/**
|
||||
* The proxy that all network operations should go through.
|
||||
*/
|
||||
Proxy getProxy();
|
||||
|
||||
/**
|
||||
* Check if the game is complete.
|
||||
* Check libraries, assets, logging files and so on.
|
||||
|
||||
@@ -53,7 +53,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameAssetDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
@@ -107,7 +107,7 @@ public final class GameAssetDownloadTask extends Task {
|
||||
flag = !file.exists();
|
||||
}
|
||||
if (flag) {
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||
task.setName(assetObject.getHash());
|
||||
dependencies.add(task);
|
||||
}
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ public final class GameAssetIndexDownloadTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameAssetIndexDownloadTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
@@ -67,7 +67,7 @@ public final class GameAssetIndexDownloadTask extends Task {
|
||||
File assetIndexFile = dependencyManager.getGameRepository().getIndexFile(version.getId(), assetIndexInfo.getId());
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(assetIndexInfo.getUrl())),
|
||||
assetIndexFile, dependencyManager.getProxy()
|
||||
assetIndexFile
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public final class GameAssetRefreshTask extends TaskResult<Collection<Pair<File,
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameAssetRefreshTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
|
||||
@@ -56,7 +56,6 @@ public final class GameDownloadTask extends Task {
|
||||
dependencies.add(new FileDownloadTask(
|
||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||
jar,
|
||||
dependencyManager.getProxy(),
|
||||
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||
));
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ public final class GameLibrariesTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameLibrariesTask(AbstractDependencyManager dependencyManager, Version version) {
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ public final class GameLoggingDownloadTask extends Task {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides proxy settings and {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
|
||||
@@ -66,7 +66,7 @@ public final class GameLoggingDownloadTask extends Task {
|
||||
LoggingInfo logging = version.getLogging().get(DownloadType.CLIENT);
|
||||
File file = dependencyManager.getGameRepository().getLoggingObject(version.getId(), version.getAssetIndex().getId(), logging);
|
||||
if (!file.exists())
|
||||
dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file, dependencyManager.getProxy()));
|
||||
dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
|
||||
public class LibraryDownloadException extends Exception {
|
||||
private final Library library;
|
||||
|
||||
public LibraryDownloadException(Library library, Throwable cause) {
|
||||
super("Unable to download library " + library, cause);
|
||||
|
||||
this.library = library;
|
||||
}
|
||||
|
||||
public Library getLibrary() {
|
||||
return library;
|
||||
}
|
||||
}
|
||||
@@ -43,19 +43,19 @@ public final class LibraryDownloadTask extends Task {
|
||||
xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz");
|
||||
|
||||
xzTask = new FileDownloadTask(NetworkUtils.toURL(url + ".pack.xz"),
|
||||
xzFile, dependencyManager.getProxy(), null, 1);
|
||||
xzFile, null, 1);
|
||||
xzTask.setSignificance(TaskSignificance.MINOR);
|
||||
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
|
||||
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
||||
file, dependencyManager.getProxy(),
|
||||
file,
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependents() {
|
||||
return library.getChecksums() != null ? Collections.singleton(xzTask) : Collections.emptySet();
|
||||
return library.getChecksums() != null ? Collections.singleton(xzTask) : Collections.singleton(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -65,20 +65,23 @@ public final class LibraryDownloadTask extends Task {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
if (isDependentsSucceeded() && library.getChecksums() != null) {
|
||||
if (!isDependentsSucceeded()) {
|
||||
// Since FileDownloadTask wraps the actual exception with another IOException.
|
||||
// We should extract it letting the error message clearer.
|
||||
Throwable t = library.getChecksums() != null ? xzTask.getLastException() : task.getLastException();
|
||||
if (t.getCause() != null && t.getCause() != t)
|
||||
throw new LibraryDownloadException(library, t.getCause());
|
||||
else
|
||||
throw new LibraryDownloadException(library, t);
|
||||
}
|
||||
|
||||
if (library.getChecksums() != null) {
|
||||
unpackLibrary(jar, FileUtils.readBytes(xzFile));
|
||||
if (!checksumValid(jar, library.getChecksums()))
|
||||
throw new IOException("Checksum failed for " + library);
|
||||
|
||||
downloaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<? extends Task> getDependencies() {
|
||||
return downloaded ? Collections.emptySet() : Collections.singleton(task);
|
||||
}
|
||||
|
||||
private static boolean checksumValid(File libPath, List<String> checksums) {
|
||||
try {
|
||||
if ((checksums == null) || (checksums.isEmpty())) {
|
||||
|
||||
+1
-2
@@ -24,7 +24,6 @@ import org.jackhuang.hmcl.task.GetTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@@ -66,7 +65,7 @@ public final class VersionJsonDownloadTask extends Task {
|
||||
RemoteVersion<?> remoteVersion = gameVersionList.getVersions(gameVersion).stream().findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot find specific version " + gameVersion + " in remote repository"));
|
||||
String jsonURL = dependencyManager.getDownloadProvider().injectURL(remoteVersion.getUrl());
|
||||
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL), Proxy.NO_PROXY, ID));
|
||||
dependencies.add(new GetTask(NetworkUtils.toURL(jsonURL), ID));
|
||||
}
|
||||
|
||||
public static final String ID = "raw_version_json";
|
||||
|
||||
@@ -20,112 +20,75 @@ 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.IOUtils;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantPool;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
|
||||
import org.jenkinsci.constant_pool_scanner.ConstantType;
|
||||
import org.jenkinsci.constant_pool_scanner.StringConstant;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.US_ASCII;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameVersion {
|
||||
private static int lessThan32(byte[] b, int x) {
|
||||
for (; x < b.length; x++)
|
||||
if (b[x] < 32)
|
||||
return x;
|
||||
return -1;
|
||||
private static Optional<String> getVersionOfClassMinecraft(ZipFile file, ZipArchiveEntry entry) throws IOException {
|
||||
ConstantPool pool = ConstantPoolScanner.parse(file.getInputStream(entry), ConstantType.STRING);
|
||||
|
||||
return StreamSupport.stream(pool.list(StringConstant.class).spliterator(), false)
|
||||
.map(StringConstant::get)
|
||||
.filter(s -> s.startsWith("Minecraft Minecraft "))
|
||||
.map(s -> s.substring("Minecraft Minecraft ".length()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
public static int matchArray(byte[] a, byte[] b) {
|
||||
for (int i = 0; i < a.length - b.length; i++) {
|
||||
int j = 1;
|
||||
for (int k = 0; k < b.length; k++) {
|
||||
if (b[k] == a[(i + k)])
|
||||
continue;
|
||||
j = 0;
|
||||
private static Optional<String> getVersionFromClassMinecraftServer(ZipFile file, ZipArchiveEntry entry) throws IOException {
|
||||
ConstantPool pool = ConstantPoolScanner.parse(file.getInputStream(entry), ConstantType.STRING);
|
||||
|
||||
List<String> list = StreamSupport.stream(pool.list(StringConstant.class).spliterator(), false)
|
||||
.map(StringConstant::get)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int idx = -1;
|
||||
|
||||
for (int i = 0; i < list.size(); ++i)
|
||||
if (list.get(i).startsWith("Can't keep up!")) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
if (j != 0)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static Optional<String> getVersionOfOldMinecraft(ZipFile file, ZipArchiveEntry entry) throws IOException {
|
||||
byte[] tmp = IOUtils.readFullyAsByteArray(file.getInputStream(entry));
|
||||
for (int i = idx - 1; i >= 0; --i)
|
||||
if (list.get(i).matches(".*[0-9].*"))
|
||||
return Optional.of(list.get(i));
|
||||
|
||||
byte[] bytes = "Minecraft Minecraft ".getBytes(US_ASCII);
|
||||
int j = matchArray(tmp, bytes);
|
||||
if (j < 0)
|
||||
return Optional.empty();
|
||||
int i = j + bytes.length;
|
||||
|
||||
if ((j = lessThan32(tmp, i)) < 0)
|
||||
return Optional.empty();
|
||||
|
||||
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(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, 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(US_ASCII);
|
||||
j = matchArray(tmp, str);
|
||||
if (j < 0) return Optional.empty();
|
||||
i = -1;
|
||||
while (j > 0) {
|
||||
if (tmp[j] >= 48 && tmp[j] <= 57) {
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
j--;
|
||||
}
|
||||
if (i == -1) return Optional.empty();
|
||||
int k = i;
|
||||
if (tmp[i + 1] >= (int) 'a' && tmp[i + 1] <= (int) 'z')
|
||||
i++;
|
||||
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, US_ASCII));
|
||||
}
|
||||
return Optional.of(result);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static Optional<String> minecraftVersion(File file) {
|
||||
if (file == null || !file.exists() || !file.isFile() || !file.canRead())
|
||||
return Optional.empty();
|
||||
|
||||
ZipFile f = null;
|
||||
ZipFile gameJar = null;
|
||||
try {
|
||||
f = new ZipFile(file);
|
||||
ZipArchiveEntry minecraft = f
|
||||
.getEntry("net/minecraft/client/Minecraft.class");
|
||||
if (minecraft != null)
|
||||
return getVersionOfOldMinecraft(f, minecraft);
|
||||
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);
|
||||
gameJar = new ZipFile(file);
|
||||
ZipArchiveEntry minecraft = gameJar.getEntry("net/minecraft/client/Minecraft.class");
|
||||
if (minecraft != null) {
|
||||
Optional<String> result = getVersionOfClassMinecraft(gameJar, minecraft);
|
||||
if (result.isPresent())
|
||||
return result;
|
||||
}
|
||||
ZipArchiveEntry minecraftServer = gameJar.getEntry("net/minecraft/server/MinecraftServer.class");
|
||||
if (minecraftServer != null)
|
||||
return getVersionFromClassMinecraftServer(gameJar, minecraftServer);
|
||||
return Optional.empty();
|
||||
} catch (IOException e) {
|
||||
return Optional.empty();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(f);
|
||||
IOUtils.closeQuietly(gameJar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ public final class CurseCompletionTask extends Task {
|
||||
updateProgress(finished.incrementAndGet(), manifest.getFiles().size());
|
||||
if (StringUtils.isBlank(file.getFileName())) {
|
||||
try {
|
||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl(), dependencyManager.getProxy()));
|
||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
||||
} catch (IOException ioe) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), ioe);
|
||||
flag.set(false);
|
||||
@@ -125,7 +125,7 @@ public final class CurseCompletionTask extends Task {
|
||||
if (StringUtils.isNotBlank(file.getFileName())) {
|
||||
File dest = new File(run, "mods/" + file.getFileName());
|
||||
if (!dest.exists())
|
||||
dependencies.add(new FileDownloadTask(file.getUrl(), dest, dependencyManager.getProxy()));
|
||||
dependencies.add(new FileDownloadTask(file.getUrl(), dest));
|
||||
}
|
||||
|
||||
// Let this task fail if the curse manifest has not been completed.
|
||||
|
||||
@@ -41,7 +41,7 @@ public final class ModManager {
|
||||
File modsDirectory = new File(repository.getRunDirectory(id), "mods");
|
||||
Consumer<File> puter = modFile -> Lang.ignoringException(() -> modCache.put(id, ModInfo.fromFile(modFile)));
|
||||
Optional.ofNullable(modsDirectory.listFiles()).map(Arrays::stream).ifPresent(files -> files.forEach(modFile -> {
|
||||
if (modFile.isDirectory() && VersionNumber.parseVersion(modFile.getName()) != null)
|
||||
if (modFile.isDirectory() && VersionNumber.parseVersion(modFile.getName()).isPresent())
|
||||
Optional.ofNullable(modFile.listFiles()).map(Arrays::stream).ifPresent(x -> x.forEach(puter));
|
||||
else
|
||||
puter.accept(modFile);
|
||||
|
||||
@@ -31,7 +31,6 @@ import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.math.BigInteger;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.logging.Level;
|
||||
@@ -79,7 +78,6 @@ public class FileDownloadTask extends Task {
|
||||
private final File file;
|
||||
private final IntegrityCheck integrityCheck;
|
||||
private final int retry;
|
||||
private final Proxy proxy;
|
||||
private final EventManager<FailedEvent<URL>> onFailed = new EventManager<>();
|
||||
private RandomAccessFile rFile;
|
||||
private InputStream stream;
|
||||
@@ -89,26 +87,16 @@ public class FileDownloadTask extends Task {
|
||||
* @param file the location that download to.
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file) {
|
||||
this(url, file, Proxy.NO_PROXY);
|
||||
this(url, file, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param proxy the proxy.
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, Proxy proxy) {
|
||||
this(url, file, proxy, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url the URL of remote file.
|
||||
* @param file the location that download to.
|
||||
* @param proxy the proxy.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, Proxy proxy, IntegrityCheck integrityCheck) {
|
||||
this(url, file, proxy, integrityCheck, 5);
|
||||
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck) {
|
||||
this(url, file, integrityCheck, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,14 +104,12 @@ public class FileDownloadTask extends Task {
|
||||
* @param file the location that download to.
|
||||
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||
* @param retry the times for retrying if downloading fails.
|
||||
* @param proxy the proxy.
|
||||
*/
|
||||
public FileDownloadTask(URL url, File file, Proxy proxy, IntegrityCheck integrityCheck, int retry) {
|
||||
public FileDownloadTask(URL url, File file, IntegrityCheck integrityCheck, int retry) {
|
||||
this.url = url;
|
||||
this.file = file;
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.retry = retry;
|
||||
this.proxy = proxy;
|
||||
|
||||
setName(file.getName());
|
||||
}
|
||||
@@ -174,7 +160,7 @@ public class FileDownloadTask extends Task {
|
||||
try {
|
||||
updateProgress(0);
|
||||
|
||||
HttpURLConnection con = NetworkUtils.createConnection(url, proxy);
|
||||
HttpURLConnection con = NetworkUtils.createConnection(url);
|
||||
con.connect();
|
||||
|
||||
if (con.getResponseCode() / 100 != 2)
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.Proxy;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.logging.Level;
|
||||
@@ -41,30 +40,24 @@ public final class GetTask extends TaskResult<String> {
|
||||
private final URL url;
|
||||
private final Charset charset;
|
||||
private final int retry;
|
||||
private final Proxy proxy;
|
||||
private final String id;
|
||||
|
||||
public GetTask(URL url) {
|
||||
this(url, Proxy.NO_PROXY);
|
||||
this(url, ID);
|
||||
}
|
||||
|
||||
public GetTask(URL url, Proxy proxy) {
|
||||
this(url, proxy, ID);
|
||||
public GetTask(URL url, String id) {
|
||||
this(url, id, UTF_8);
|
||||
}
|
||||
|
||||
public GetTask(URL url, Proxy proxy, String id) {
|
||||
this(url, proxy, id, UTF_8);
|
||||
public GetTask(URL url, String id, Charset charset) {
|
||||
this(url, id, charset, 5);
|
||||
}
|
||||
|
||||
public GetTask(URL url, Proxy proxy, String id, Charset charset) {
|
||||
this(url, proxy, id, charset, 5);
|
||||
}
|
||||
|
||||
public GetTask(URL url, Proxy proxy, String id, Charset charset, int retry) {
|
||||
public GetTask(URL url, String id, Charset charset, int retry) {
|
||||
this.url = url;
|
||||
this.charset = charset;
|
||||
this.retry = retry;
|
||||
this.proxy = proxy;
|
||||
this.id = id;
|
||||
|
||||
setName(url.toString());
|
||||
@@ -88,7 +81,7 @@ public final class GetTask extends TaskResult<String> {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download, repeat times: " + time);
|
||||
try {
|
||||
updateProgress(0);
|
||||
HttpURLConnection conn = NetworkUtils.createConnection(url, proxy);
|
||||
HttpURLConnection conn = NetworkUtils.createConnection(url);
|
||||
InputStream input = conn.getInputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
|
||||
@@ -63,6 +63,16 @@ public abstract class Task {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
private Throwable lastException = null;
|
||||
|
||||
public Throwable getLastException() {
|
||||
return lastException;
|
||||
}
|
||||
|
||||
void setLastException(Throwable e) {
|
||||
lastException = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheduler that decides how this task runs.
|
||||
*/
|
||||
|
||||
@@ -190,6 +190,7 @@ public final class TaskExecutor {
|
||||
} catch (SilentException | RejectedExecutionException e) {
|
||||
// do nothing
|
||||
} catch (Exception e) {
|
||||
task.setLastException(e);
|
||||
lastException = e;
|
||||
variables.set(LAST_EXCEPTION_ID, e);
|
||||
if (task.getSignificance().shouldLog()) {
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* If a version string contains '-', a {@link ComposedVersionNumber}
|
||||
* will be generated.
|
||||
*
|
||||
* Formats like 1.7.10-OptiFine, 1.12.2-Forge
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ComposedVersionNumber extends VersionNumber {
|
||||
List<VersionNumber> composed;
|
||||
|
||||
public static boolean isComposedVersionNumber(String version) {
|
||||
return version.contains("-");
|
||||
}
|
||||
|
||||
ComposedVersionNumber(String version) {
|
||||
composed = Arrays.stream(version.split("-"))
|
||||
.map(VersionNumber::asVersion)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(composed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return composed.stream().map(VersionNumber::toString).collect(Collectors.joining("-"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* A deserializer that supports deserializing strings and **numbers** into enums.
|
||||
*
|
||||
* @author yushijinhun
|
||||
*/
|
||||
public class EnumOrdinalDeserializer<T extends Enum<T>> implements JsonDeserializer<T> {
|
||||
|
||||
private Map<String, T> mapping = new HashMap<>();
|
||||
|
||||
public EnumOrdinalDeserializer(Class<T> enumClass) {
|
||||
for (T constant : enumClass.getEnumConstants()) {
|
||||
mapping.put(String.valueOf(constant.ordinal()), constant);
|
||||
String name = constant.name();
|
||||
try {
|
||||
SerializedName annotation = enumClass.getField(name).getAnnotation(SerializedName.class);
|
||||
if (annotation != null) {
|
||||
name = annotation.value();
|
||||
for (String alternate : annotation.alternate()) {
|
||||
mapping.put(alternate, constant);
|
||||
}
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
mapping.put(name, constant);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
return mapping.get(json.getAsString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -28,57 +31,37 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public final class IntVersionNumber extends VersionNumber {
|
||||
|
||||
private final List<Integer> version;
|
||||
final List<Integer> version;
|
||||
|
||||
IntVersionNumber(List<Integer> version) {
|
||||
this.version = version;
|
||||
public static boolean isIntVersionNumber(String version) {
|
||||
return version.chars().noneMatch(ch -> ch != '.' && (ch < '0' || ch > '9'))
|
||||
&& !version.contains("..") && StringUtils.isNotBlank(version);
|
||||
}
|
||||
|
||||
IntVersionNumber(String version) {
|
||||
if (!isIntVersionNumber(version))
|
||||
throw new IllegalArgumentException("The version " + version + " is malformed, only dots and digits are allowed.");
|
||||
|
||||
List<Integer> versions = Arrays.stream(version.split("\\."))
|
||||
.map(Integer::parseInt)
|
||||
.collect(Collectors.toList());
|
||||
while (!versions.isEmpty() && versions.get(versions.size() - 1) == 0)
|
||||
versions.remove(versions.size() - 1);
|
||||
|
||||
this.version = versions;
|
||||
}
|
||||
|
||||
public int get(int index) {
|
||||
return version.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VersionNumber o) {
|
||||
if (!(o instanceof IntVersionNumber))
|
||||
return 0;
|
||||
IntVersionNumber other = (IntVersionNumber) o;
|
||||
int len = Math.max(this.version.size(), other.version.size());
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int thisInt = Lang.get(this.version, i).orElse(0);
|
||||
int otherInt = Lang.get(other.version, i).orElse(0);
|
||||
if (thisInt != otherInt)
|
||||
return Integer.compare(thisInt, otherInt);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = 3;
|
||||
for (int i : version)
|
||||
hash = 83 * hash + i;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof VersionNumber))
|
||||
return false;
|
||||
if (!(obj instanceof IntVersionNumber))
|
||||
return true;
|
||||
IntVersionNumber other = (IntVersionNumber) obj;
|
||||
if (version.size() != other.version.size())
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < version.size(); ++i)
|
||||
if (!version.get(i).equals(other.version.get(i)))
|
||||
return false;
|
||||
return true;
|
||||
return Objects.hash(version);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.join(".", version.stream().map(Object::toString).collect(Collectors.toList()));
|
||||
return version.stream().map(Object::toString).collect(Collectors.joining("."));
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public final class Lang {
|
||||
*/
|
||||
@SafeVarargs
|
||||
public static <K, V> Map<K, V> mapOf(Pair<K, V>... pairs) {
|
||||
HashMap<K, V> map = new HashMap<>();
|
||||
Map<K, V> map = new LinkedHashMap<>();
|
||||
for (Pair<K, V> pair : pairs)
|
||||
map.put(pair.getKey(), pair.getValue());
|
||||
return map;
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static javafx.collections.FXCollections.unmodifiableObservableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.WeakListChangeListener;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
*/
|
||||
public final class MappedObservableList {
|
||||
private MappedObservableList() {
|
||||
}
|
||||
|
||||
private static class ReferenceHolder implements InvalidationListener {
|
||||
@SuppressWarnings("unused")
|
||||
private Object ref;
|
||||
|
||||
ReferenceHolder(Object ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
private static class MappedObservableListUpdater<T, U> implements ListChangeListener<T> {
|
||||
private ObservableList<T> origin;
|
||||
private ObservableList<U> target;
|
||||
private Function<T, U> mapper;
|
||||
|
||||
// If we directly synchronize changes to target, each operation on target will cause a event to be fired.
|
||||
// So we first write changes to buffer. After all the changes are processed, we use target.setAll to synchronize the changes.
|
||||
private List<U> buffer;
|
||||
|
||||
MappedObservableListUpdater(ObservableList<T> origin, ObservableList<U> target, Function<T, U> mapper) {
|
||||
this.origin = origin;
|
||||
this.target = target;
|
||||
this.mapper = mapper;
|
||||
this.buffer = new ArrayList<>(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChanged(Change<? extends T> change) {
|
||||
// cache removed elements to reduce calls to mapper
|
||||
Map<T, LinkedList<U>> cache = new IdentityHashMap<>();
|
||||
|
||||
while (change.next()) {
|
||||
int from = change.getFrom();
|
||||
int to = change.getTo();
|
||||
|
||||
if (change.wasPermutated()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
U[] temp = (U[]) new Object[to - from];
|
||||
for (int i = 0; i < temp.length; i++) {
|
||||
temp[i] = buffer.get(from + i);
|
||||
}
|
||||
|
||||
for (int idx = from; idx < to; idx++) {
|
||||
buffer.set(change.getPermutation(idx), temp[idx - from]);
|
||||
}
|
||||
} else {
|
||||
if (change.wasRemoved()) {
|
||||
List<? extends T> originRemoved = change.getRemoved();
|
||||
List<U> targetRemoved = buffer.subList(from, from + originRemoved.size());
|
||||
for (int i = 0; i < targetRemoved.size(); i++) {
|
||||
pushCache(cache, originRemoved.get(i), targetRemoved.get(i));
|
||||
}
|
||||
targetRemoved.clear();
|
||||
}
|
||||
if (change.wasAdded()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
U[] toAdd = (U[]) new Object[to - from];
|
||||
for (int i = 0; i < toAdd.length; i++) {
|
||||
toAdd[i] = map(cache, origin.get(from + i));
|
||||
}
|
||||
buffer.addAll(from, Arrays.asList(toAdd));
|
||||
}
|
||||
}
|
||||
}
|
||||
target.setAll(buffer);
|
||||
}
|
||||
|
||||
private void pushCache(Map<T, LinkedList<U>> cache, T key, U value) {
|
||||
cache.computeIfAbsent(key, any -> new LinkedList<>())
|
||||
.push(value);
|
||||
}
|
||||
|
||||
private U map(Map<T, LinkedList<U>> cache, T key) {
|
||||
LinkedList<U> stack = cache.get(key);
|
||||
if (stack != null && !stack.isEmpty()) {
|
||||
return stack.pop();
|
||||
}
|
||||
return mapper.apply(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods creates a mapping of {@code origin}, using {@code mapper} as the converter.
|
||||
*
|
||||
* If an item is added to {@code origin}, {@code mapper} will be invoked to create a corresponding item, which will also be added to the returned {@code ObservableList}.
|
||||
* If an item is removed from {@code origin}, the corresponding item in the returned {@code ObservableList} will also be removed.
|
||||
* If {@code origin} is permutated, the returned {@code ObservableList} will also be permutated in the same way.
|
||||
*
|
||||
* The returned {@code ObservableList} is unmodifiable.
|
||||
*/
|
||||
public static <T, U> ObservableList<U> create(ObservableList<T> origin, Function<T, U> mapper) {
|
||||
// create a already-synchronized target ObservableList<U>
|
||||
ObservableList<U> target = origin.stream()
|
||||
.map(mapper)
|
||||
.collect(toCollection(FXCollections::observableArrayList));
|
||||
|
||||
// then synchronize further changes to target
|
||||
ListChangeListener<T> listener = new MappedObservableListUpdater<>(origin, target, mapper);
|
||||
|
||||
// let target hold a reference to listener to prevent listener being garbage-collected before target is garbage-collected
|
||||
target.addListener(new ReferenceHolder(listener));
|
||||
|
||||
// let origin hold a weak reference to listener, so that target can be garbage-collected when it's no longer used
|
||||
origin.addListener(new WeakListChangeListener<>(listener));
|
||||
|
||||
// ref graph:
|
||||
// target ------> listener <-weak- origin
|
||||
// <------ ------>
|
||||
|
||||
return unmodifiableObservableList(target);
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,8 @@ public final class NetworkUtils {
|
||||
NetworkUtils.userAgentSupplier = Objects.requireNonNull(userAgentSupplier);
|
||||
}
|
||||
|
||||
public static HttpURLConnection createConnection(URL url, Proxy proxy) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy);
|
||||
public static HttpURLConnection createConnection(URL url) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setDoInput(true);
|
||||
connection.setUseCaches(false);
|
||||
connection.setConnectTimeout(15000);
|
||||
@@ -59,11 +59,7 @@ public final class NetworkUtils {
|
||||
}
|
||||
|
||||
public static String doGet(URL url) throws IOException {
|
||||
return IOUtils.readFullyAsString(createConnection(url, Proxy.NO_PROXY).getInputStream());
|
||||
}
|
||||
|
||||
public static String doGet(URL url, Proxy proxy) throws IOException {
|
||||
return IOUtils.readFullyAsString(createConnection(url, proxy).getInputStream());
|
||||
return IOUtils.readFullyAsString(createConnection(url).getInputStream());
|
||||
}
|
||||
|
||||
public static String doPost(URL u, Map<String, String> params) throws IOException {
|
||||
@@ -80,14 +76,10 @@ public final class NetworkUtils {
|
||||
return doPost(u, post, "application/x-www-form-urlencoded");
|
||||
}
|
||||
|
||||
public static String doPost(URL u, String post, String contentType) throws IOException {
|
||||
return doPost(u, post, contentType, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public static String doPost(URL url, String post, String contentType, Proxy proxy) throws IOException {
|
||||
public static String doPost(URL url, String post, String contentType) throws IOException {
|
||||
byte[] bytes = post.getBytes(UTF_8);
|
||||
|
||||
HttpURLConnection con = createConnection(url, proxy);
|
||||
HttpURLConnection con = createConnection(url);
|
||||
con.setRequestMethod("POST");
|
||||
con.setDoOutput(true);
|
||||
con.setRequestProperty("Content-Type", contentType + "; charset=utf-8");
|
||||
@@ -120,11 +112,7 @@ public final class NetworkUtils {
|
||||
}
|
||||
|
||||
public static String detectFileName(URL url) throws IOException {
|
||||
return detectFileName(url, Proxy.NO_PROXY);
|
||||
}
|
||||
|
||||
public static String detectFileName(URL url, Proxy proxy) throws IOException {
|
||||
HttpURLConnection conn = createConnection(url, proxy);
|
||||
HttpURLConnection conn = createConnection(url);
|
||||
conn.connect();
|
||||
if (conn.getResponseCode() / 100 != 2)
|
||||
throw new IOException("Response code " + conn.getResponseCode());
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
|
||||
/**
|
||||
* Helper class for implementing {@link Observable}.
|
||||
*
|
||||
* @author yushijinhun
|
||||
*/
|
||||
public class ObservableHelper implements Observable, InvalidationListener {
|
||||
|
||||
private List<InvalidationListener> listeners = new CopyOnWriteArrayList<>();
|
||||
private Observable source;
|
||||
|
||||
public ObservableHelper(Observable source) {
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be called from any thread.
|
||||
*/
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be called from any thread.
|
||||
*/
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public void invalidate() {
|
||||
listeners.forEach(it -> it.invalidated(source));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidated(Observable observable) {
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
public void receiveUpdatesFrom(Observable observable) {
|
||||
observable.removeListener(this); // remove the previously added listener(if any)
|
||||
observable.addListener(this);
|
||||
}
|
||||
}
|
||||
@@ -34,14 +34,6 @@ public final class StringVersionNumber extends VersionNumber {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VersionNumber o) {
|
||||
if (!(o instanceof StringVersionNumber))
|
||||
return 0;
|
||||
|
||||
return version.compareTo(((StringVersionNumber) o).version);
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
@@ -56,15 +48,4 @@ public final class StringVersionNumber extends VersionNumber {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof VersionNumber))
|
||||
return false;
|
||||
|
||||
if (!(obj instanceof StringVersionNumber))
|
||||
return true;
|
||||
|
||||
return version.equals(((StringVersionNumber) obj).version);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,10 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The formatted version number represents a version string.
|
||||
@@ -29,52 +26,76 @@ import java.util.Optional;
|
||||
*/
|
||||
public abstract class VersionNumber implements Comparable<VersionNumber> {
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if there are some characters excluding digits and dots.
|
||||
* @param version version string in form x.x.x
|
||||
* @return the int version number
|
||||
*/
|
||||
public static IntVersionNumber asIntVersionNumber(String version) {
|
||||
if (version.chars().filter(ch -> ch != '.' && (ch < '0' || ch > '9')).count() > 0
|
||||
|| version.contains("..") || StringUtils.isBlank(version))
|
||||
throw new IllegalArgumentException("The version " + version + " is malformed, only dots and digits are allowed.");
|
||||
|
||||
String[] s = version.split("\\.");
|
||||
ArrayList<Integer> versions = new ArrayList<>(s.length);
|
||||
for (String value : s) versions.add(Integer.parseInt(value));
|
||||
while (!versions.isEmpty() && versions.get(versions.size() - 1) == 0)
|
||||
versions.remove(versions.size() - 1);
|
||||
|
||||
return new IntVersionNumber(Collections.unmodifiableList(versions));
|
||||
}
|
||||
|
||||
public static StringVersionNumber asStringVersionNumber(String version) {
|
||||
return new StringVersionNumber(version);
|
||||
}
|
||||
|
||||
public static VersionNumber asVersion(String version) {
|
||||
Objects.requireNonNull(version);
|
||||
try {
|
||||
return asIntVersionNumber(version);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return asStringVersionNumber(version);
|
||||
}
|
||||
if (ComposedVersionNumber.isComposedVersionNumber(version))
|
||||
return new ComposedVersionNumber(version);
|
||||
else if (IntVersionNumber.isIntVersionNumber(version))
|
||||
return new IntVersionNumber(version);
|
||||
else
|
||||
return new StringVersionNumber(version);
|
||||
}
|
||||
|
||||
public static Optional<String> parseVersion(String str) {
|
||||
if (str.chars().anyMatch(it -> it != '.' && (it < '0' || it > '9')) || StringUtils.isBlank(str))
|
||||
if (IntVersionNumber.isIntVersionNumber(str))
|
||||
return Optional.of(new IntVersionNumber(str).toString());
|
||||
else
|
||||
return Optional.empty();
|
||||
String[] s = str.split("\\.");
|
||||
for (String i : s)
|
||||
if (StringUtils.isBlank(i))
|
||||
return Optional.empty();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int last = s.length - 1;
|
||||
for (int i = s.length - 1; i >= 0; --i)
|
||||
if (Integer.parseInt(s[i]) == 0)
|
||||
last = i;
|
||||
for (int i = 0; i < last; ++i)
|
||||
builder.append(s[i]).append(".");
|
||||
return Optional.of(builder.append(s[last]).toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(VersionNumber o) {
|
||||
return COMPARATOR.compare(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
else return toString().equals(obj.toString());
|
||||
}
|
||||
|
||||
private static <T extends Comparable<T>> int compareTo(List<T> a, List<T> b) {
|
||||
int i;
|
||||
for (i = 0; i < a.size() && i < b.size(); ++i) {
|
||||
int res = a.get(i).compareTo(b.get(i));
|
||||
if (res != 0)
|
||||
return res;
|
||||
}
|
||||
if (i < a.size()) return 1;
|
||||
else if (i < b.size()) return -1;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
public static final Comparator<VersionNumber> COMPARATOR = new Comparator<VersionNumber>() {
|
||||
@Override
|
||||
public int compare(VersionNumber a, VersionNumber b) {
|
||||
if (a == null || b == null)
|
||||
return 0;
|
||||
else {
|
||||
if (a instanceof ComposedVersionNumber) {
|
||||
if (b instanceof ComposedVersionNumber)
|
||||
return compareTo(((ComposedVersionNumber) a).composed, ((ComposedVersionNumber) b).composed);
|
||||
else
|
||||
return compare(((ComposedVersionNumber) a).composed.get(0), b);
|
||||
} else if (a instanceof IntVersionNumber) {
|
||||
if (b instanceof ComposedVersionNumber)
|
||||
return -compare(b, a);
|
||||
else if (b instanceof IntVersionNumber)
|
||||
return compareTo(((IntVersionNumber) a).version, ((IntVersionNumber) b).version);
|
||||
else if (b instanceof StringVersionNumber)
|
||||
return a.toString().compareTo(b.toString());
|
||||
} else if (a instanceof StringVersionNumber) {
|
||||
if (b instanceof ComposedVersionNumber)
|
||||
return -compare(b, a);
|
||||
else if (b instanceof StringVersionNumber)
|
||||
return a.toString().compareTo(b.toString());
|
||||
else if (b instanceof IntVersionNumber)
|
||||
return a.toString().compareTo(b.toString());
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unrecognized VersionNumber " + a + " and " + b);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Debug
|
||||
Release
|
||||
.vs
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
@@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{672B1019-E741-4C0D-A986-627E2ACE157B}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>HMCL</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141_xp</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141_xp</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>false</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="HMCL.h" />
|
||||
<ClInclude Include="Resource.h" />
|
||||
<ClInclude Include="stdafx.h" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="HMCL.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="HMCL.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="HMCL.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="targetver.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Resource.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="HMCL.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HMCL.cpp">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="HMCL.rc">
|
||||
<Filter>资源文件</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="HMCL.ico">
|
||||
<Filter>资源文件</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user