Compare commits

..
28 Commits
Author SHA1 Message Date
huanghongxun 663e1ef7ab Merge pull request #332 from xfl03/javafx
Fix #325 #296
2018-05-20 19:40:39 +08:00
Alexander Xia 1a47d355f0 Fix #325 #296 2018-05-20 19:12:12 +08:00
huangyuhui e407994103 Fixed #331 2018-05-17 16:46:16 +08:00
huangyuhui bda2149c02 Fix #326 2018-04-30 01:59:33 +08:00
huangyuhui b925423d61 Fixded #324 2018-04-26 22:15:27 +08:00
huangyuhui f3906a37bf Fixed #321 2018-04-26 13:14:14 +08:00
huangyuhui a1e115ea6b Ignore some exceptions 2018-03-09 16:54:28 +08:00
huangyuhui 03383510ef Disable accept button in AddAccountPage 2018-03-09 16:45:12 +08:00
huangyuhui e8cf9644eb Fix IllegalArgumentException: Invalid UUID String 2018-03-09 16:31:39 +08:00
huangyuhui dbfcf1facc Try to fix empty stack of StackContainerPane 2018-03-09 16:24:02 +08:00
huangyuhui 44aff94937 Remove error 2018-03-06 11:25:15 +08:00
huangyuhui fb1fc92b7c Fix optifine 2018-03-06 11:19:21 +08:00
huangyuhui 71cfa64b16 Fix StackContainerPane 2018-03-06 10:19:37 +08:00
huangyuhui b200105a85 Fix Liteloader installing of BMCLAPI 2018-03-06 10:13:48 +08:00
huanghongxun ce83f3c5e1 Merge pull request #297 from andylizi/switch-to-https
Switch to Https for most links.
2018-03-05 20:57:30 +08:00
huanghongxun 57d3275856 Merge branch 'javafx' into switch-to-https 2018-03-05 20:56:26 +08:00
huangyuhui eb1a109afc Fix IllegalArgumentException: Folder parameter must be a valid folder 2018-03-05 20:36:38 +08:00
huangyuhui a03e3fea12 Logs stopApplication stack trace 2018-03-05 20:23:23 +08:00
huangyuhui c60ae111ef Progress bar in account page 2018-03-05 20:07:30 +08:00
huangyuhui 5eb46395ae Close popup when deleted an account 2018-03-05 19:13:58 +08:00
huangyuhui db08b61cfa Fix #301 2018-03-05 19:02:13 +08:00
huangyuhui 41d313baff Fix #303 2018-03-04 23:35:42 +08:00
huangyuhui 58c3ed9254 Fixed #302 2018-03-04 23:33:08 +08:00
huangyuhui affe36a568 Fix update again 2018-03-04 23:29:27 +08:00
huangyuhui 9ba6cddde7 Fix update 2018-03-04 19:09:11 +08:00
huangyuhui 5656c9f351 update update_link.php 2018-03-04 16:54:35 +08:00
huangyuhui ecf24a746e Fix crashing when unable to save version setting 2018-03-04 15:33:54 +08:00
andylizi 76c254ee98 Switch the download links to HTTPS 2018-02-28 17:23:49 +08:00
67 changed files with 943 additions and 393 deletions

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ if (buildnumber == null)
def versionroot = System.getenv("VERSION_ROOT")
if (versionroot == null)
versionroot = "3.0"
versionroot = "3.1"
String mavenVersion = versionroot + '.' + buildnumber
version = mavenVersion
@@ -31,7 +31,13 @@ import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
@@ -49,6 +55,14 @@ public final class Launcher extends Application {
Controllers.initialize(primaryStage);
primaryStage.setResizable(false);
primaryStage.setScene(Controllers.getScene());
UPDATE_CHECKER.process(false)
.then(Task.of(Schedulers.javafx(), () -> {
if (UPDATE_CHECKER.isOutOfDate())
Controllers.showUpdate();
}))
.start();
primaryStage.show();
} catch (Throwable e) {
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
@@ -59,19 +73,14 @@ public final class Launcher extends Application {
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
try {
Logging.start();
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args));
Logging.LOG.info("*** " + TITLE + " ***");
UPDATE_CHECKER.process(false)
.then(Task.of(Schedulers.javafx(), () -> {
if (UPDATE_CHECKER.isOutOfDate())
Controllers.showUpdate();
}))
.start();
launch(args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
@@ -79,25 +88,28 @@ public final class Launcher extends Application {
}
public static void stopApplication() {
JFXUtilities.runInFX(() -> {
stopWithoutPlatform();
Platform.exit();
});
}
public static void stopWithoutPlatform() {
Logging.LOG.info("Stopping application without JavaFX Toolkit");
Logging.LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
JFXUtilities.runInFX(() -> {
if (Controllers.getStage() == null)
return;
Controllers.getStage().close();
Logging.LOG.info("Shutting down executor services.");
Schedulers.shutdown();
Controllers.shutdown();
Platform.exit();
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
});
}
public static void stopWithoutPlatform() {
Logging.LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
JFXUtilities.runInFX(() -> {
if (Controllers.getStage() == null)
return;
Controllers.getStage().close();
Schedulers.shutdown();
Controllers.shutdown();
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
});
}
@@ -117,6 +129,25 @@ public final class Launcher extends Application {
}
}
public static List<File> getCurrentJarFiles() {
List<File> result = new LinkedList<>();
if (Launcher.class.getClassLoader() instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) Launcher.class.getClassLoader()).getURLs();
for (URL u : urls)
try {
File f = new File(u.toURI());
if (f.isFile() && (f.getName().endsWith(".exe") || f.getName().endsWith(".jar")))
result.add(f);
} catch (URISyntaxException e) {
return null;
}
}
if (result.isEmpty())
return null;
else
return result;
}
public static String i18n(String key) {
try {
return RESOURCE_BUNDLE.getString(key);
@@ -26,9 +26,8 @@ public final class Main {
public static void main(String[] args) {
String currentDirectory = new File("").getAbsolutePath();
Logging.LOG.info("Current directory: " + currentDirectory);
if (currentDirectory.contains("!")) {
Logging.LOG.severe("Exclamation mark(!) is not allowed in the path where HMCL is in. Forcibly exit.");
System.err.println("Exclamation mark(!) is not allowed in the path where HMCL is in. Forcibly exit.");
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
String message = "Exclamation mark(!) is not allowed in the path where HMCL is in.\nThe path is " + currentDirectory;
@@ -37,4 +36,5 @@ public final class Main {
} else
Launcher.main(args);
}
}
@@ -173,10 +173,20 @@ public class HMCLGameRepository extends DefaultGameRepository {
return new File(getVersionRoot(id), "icon.png");
}
public void saveVersionSetting(String id) {
public boolean saveVersionSetting(String id) {
if (!versionSettings.containsKey(id))
return;
Lang.invoke(() -> FileUtils.writeText(getVersionSettingFile(id), GSON.toJson(versionSettings.get(id))));
return false;
File file = getVersionSettingFile(id);
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
return false;
try {
FileUtils.writeText(file, GSON.toJson(versionSettings.get(id)));
return true;
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e);
return false;
}
}
public boolean forbidsVersion(String id) {
@@ -19,12 +19,14 @@ package org.jackhuang.hmcl.game;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.application.Platform;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.setting.LauncherVisibility;
@@ -45,13 +47,14 @@ import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public final class LauncherHelper {
public static final LauncherHelper INSTANCE = new LauncherHelper();
private LauncherHelper(){}
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(() -> {});
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
public void launch(Profile profile, Account account, String selectedVersion, File scriptFile) {
if (account == null)
@@ -64,7 +67,10 @@ public final class LauncherHelper {
Platform.runLater(() -> {
try {
checkGameState(profile, setting, version, () -> Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile)));
checkGameState(profile, setting, version, () -> {
Controllers.dialog(launchingStepsPane);
Schedulers.newThread().schedule(() -> launch0(profile, account, selectedVersion, scriptFile));
});
} catch (InterruptedException ignore) {
}
});
@@ -73,12 +79,11 @@ public final class LauncherHelper {
private void launch0(Profile profile, Account account, String selectedVersion, File scriptFile) {
GameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = repository.getResolvedVersion(selectedVersion);
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
VersionSetting setting = profile.getVersionSetting(selectedVersion);
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> Controllers.dialog(launchingStepsPane))
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)))
TaskExecutor executor = Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
.then(variables -> {
if (setting.isNotCheckGame())
return null;
@@ -100,7 +105,6 @@ public final class LauncherHelper {
}
} catch (AuthenticationException e) {
variables.set("account", DialogController.logIn(account));
JFXUtilities.runInFX(() -> Controllers.dialog(launchingStepsPane));
}
}))
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING)))
@@ -127,13 +131,15 @@ public final class LauncherHelper {
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
Launcher.stopApplication();
else
launchingStepsPane.setCancel(() -> {
launchingStepsPane.setCancel(it -> {
process.stop();
Controllers.closeDialog();
Controllers.closeDialog(it);
});
} else
Platform.runLater(() ->
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath())));
Platform.runLater(() -> {
Controllers.closeDialog(launchingStepsPane);
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
});
}))
.executor();
@@ -153,12 +159,11 @@ public final class LauncherHelper {
public void onStop(boolean success, TaskExecutor executor) {
if (!success) {
Platform.runLater(() -> {
Controllers.closeDialog(launchingStepsPane);
if (executor.getLastException() != null)
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
scriptFile == null ? Launcher.i18n("launch.failed") : Launcher.i18n("version.launch_script.failed"),
MessageBox.ERROR_MESSAGE, Controllers::closeDialog);
else
Controllers.closeDialog();
MessageBox.ERROR_MESSAGE);
});
}
launchingStepsPane.setExecutor(null);
@@ -169,7 +174,7 @@ public final class LauncherHelper {
}
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
boolean flag = false, suggest = true;
boolean flag = false;
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
JavaVersion java = setting.getJavaVersion();
@@ -180,36 +185,32 @@ public final class LauncherHelper {
flag = true;
}
if (java.getParsedVersion() < JavaVersion.JAVA_8) {
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
Controllers.dialog(Launcher.i18n("launch.advice.java9"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
suggest = false;
flag = true;
}
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
Controllers.dialog(Launcher.i18n("launch.advice.different_platform"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
Controllers.dialog(Launcher.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (flag) {
if (suggest && Controllers.getDialogContent() instanceof MessageDialogPane)
((MessageDialogPane) Controllers.getDialogContent()).disableClosingDialog();
} else
if (!flag)
onAccept.run();
}
@@ -219,10 +220,11 @@ public final class LauncherHelper {
}
public void emitStatus(LoadingState state) {
if (state == LoadingState.DONE)
Controllers.closeDialog(launchingStepsPane);
launchingStepsPane.setTitle(state.getLocalizedMessage());
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
Controllers.dialog(launchingStepsPane);
}
private void checkExit(LauncherVisibility v) {
@@ -357,7 +359,9 @@ public final class LauncherHelper {
// Never come to here.
break;
case KEEP:
// No operations here
Platform.runLater(() -> {
emitStatus(LoadingState.DONE);
});
break;
case HIDE:
Platform.runLater(() -> {
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.util.JavaVersion;
import java.util.*;
final class Config {
public final class Config implements Cloneable {
@SerializedName("last")
private String selectedProfile = "";
@@ -97,7 +97,6 @@ final class Config {
public void setSelectedProfile(String selectedProfile) {
this.selectedProfile = selectedProfile;
Settings.INSTANCE.save();
}
public String getBackgroundImage() {
@@ -106,7 +105,6 @@ final class Config {
public void setBackgroundImage(String backgroundImage) {
this.backgroundImage = backgroundImage;
Settings.INSTANCE.save();
}
public int getBackgroundImageType() {
@@ -115,7 +113,6 @@ final class Config {
public void setBackgroundImageType(int backgroundImageType) {
this.backgroundImageType = backgroundImageType;
Settings.INSTANCE.save();
}
public String getCommonDirectory() {
@@ -124,7 +121,6 @@ final class Config {
public void setCommonDirectory(String commonDirectory) {
this.commonDirectory = commonDirectory;
Settings.INSTANCE.save();
}
public boolean isHasProxy() {
@@ -133,7 +129,6 @@ final class Config {
public void setHasProxy(boolean hasProxy) {
this.hasProxy = hasProxy;
Settings.INSTANCE.save();
}
public boolean isHasProxyAuth() {
@@ -142,7 +137,6 @@ final class Config {
public void setHasProxyAuth(boolean hasProxyAuth) {
this.hasProxyAuth = hasProxyAuth;
Settings.INSTANCE.save();
}
public int getProxyType() {
@@ -151,7 +145,6 @@ final class Config {
public void setProxyType(int proxyType) {
this.proxyType = proxyType;
Settings.INSTANCE.save();
}
public String getProxyHost() {
@@ -160,7 +153,6 @@ final class Config {
public void setProxyHost(String proxyHost) {
this.proxyHost = proxyHost;
Settings.INSTANCE.save();
}
public String getProxyPort() {
@@ -169,7 +161,6 @@ final class Config {
public void setProxyPort(String proxyPort) {
this.proxyPort = proxyPort;
Settings.INSTANCE.save();
}
public String getProxyUser() {
@@ -178,7 +169,6 @@ final class Config {
public void setProxyUser(String proxyUser) {
this.proxyUser = proxyUser;
Settings.INSTANCE.save();
}
public String getProxyPass() {
@@ -187,7 +177,6 @@ final class Config {
public void setProxyPass(String proxyPass) {
this.proxyPass = proxyPass;
Settings.INSTANCE.save();
}
public String getTheme() {
@@ -196,7 +185,6 @@ final class Config {
public void setTheme(String theme) {
this.theme = theme;
Settings.INSTANCE.save();
}
public List<JavaVersion> getJava() {
@@ -205,7 +193,6 @@ final class Config {
public void setJava(List<JavaVersion> java) {
this.java = java;
Settings.INSTANCE.save();
}
public String getLocalization() {
@@ -214,7 +201,6 @@ final class Config {
public void setLocalization(String localization) {
this.localization = localization;
Settings.INSTANCE.save();
}
public int getDownloadType() {
@@ -223,7 +209,6 @@ final class Config {
public void setDownloadType(int downloadType) {
this.downloadType = downloadType;
Settings.INSTANCE.save();
}
public Map<String, Profile> getConfigurations() {
@@ -232,7 +217,6 @@ final class Config {
public void setConfigurations(Map<String, Profile> configurations) {
this.configurations = configurations;
Settings.INSTANCE.save();
}
public List<Map<Object, Object>> getAccounts() {
@@ -241,7 +225,6 @@ final class Config {
public void setAccounts(List<Map<Object, Object>> accounts) {
this.accounts = accounts;
Settings.INSTANCE.save();
}
public String getSelectedAccount() {
@@ -250,7 +233,6 @@ final class Config {
public void setSelectedAccount(String selectedAccount) {
this.selectedAccount = selectedAccount;
Settings.INSTANCE.save();
}
public String getFontFamily() {
@@ -259,7 +241,6 @@ final class Config {
public void setFontFamily(String fontFamily) {
this.fontFamily = fontFamily;
Settings.INSTANCE.save();
}
public double getFontSize() {
@@ -268,7 +249,6 @@ final class Config {
public void setFontSize(double fontSize) {
this.fontSize = fontSize;
Settings.INSTANCE.save();
}
public int getLogLines() {
@@ -285,6 +265,9 @@ final class Config {
public void setAuthlibInjectorServerURLs(Set<String> authlibInjectorServerURLs) {
this.authlibInjectorServerURLs = authlibInjectorServerURLs;
Settings.INSTANCE.save();
}
public Config clone() {
return Settings.GSON.fromJson(Settings.GSON.toJson(this), Config.class);
}
}
@@ -28,7 +28,7 @@ import java.util.List;
public final class DownloadProviders {
private DownloadProviders() {}
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(Settings.INSTANCE.getLocale() == Locales.ZH_CN), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(new MojangDownloadProvider(), BMCLAPIDownloadProvider.INSTANCE, CurseCDNDownloadProvider.INSTANCE);
public static DownloadProvider getDownloadProvider(int index) {
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(DOWNLOAD_PROVIDERS.get(0));
@@ -55,7 +55,8 @@ public class Settings {
public static final String DEFAULT_PROFILE = "Default";
public static final String HOME_PROFILE = "Home";
public static final File SETTINGS_FILE = new File("hmcl.json").getAbsoluteFile();
public static final String SETTINGS_FILE_NAME = "hmcl.json";
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
public static final Settings INSTANCE = new Settings();
@@ -144,6 +145,7 @@ public class Settings {
super.invalidated();
SETTINGS.setCommonDirectory(get());
save();
}
};
@@ -168,6 +170,7 @@ public class Settings {
public void setLocale(Locales.SupportedLocale locale) {
this.locale = locale;
SETTINGS.setLocalization(Locales.getNameByLocale(locale));
save();
}
private Proxy proxy = Proxy.NO_PROXY;
@@ -185,6 +188,7 @@ public class Settings {
public void setProxyType(Proxy.Type proxyType) {
this.proxyType = proxyType;
SETTINGS.setProxyType(Proxies.PROXIES.indexOf(proxyType));
save();
loadProxy();
}
@@ -194,6 +198,7 @@ public class Settings {
public void setProxyHost(String proxyHost) {
SETTINGS.setProxyHost(proxyHost);
save();
}
public String getProxyPort() {
@@ -202,6 +207,7 @@ public class Settings {
public void setProxyPort(String proxyPort) {
SETTINGS.setProxyPort(proxyPort);
save();
}
public String getProxyUser() {
@@ -210,6 +216,7 @@ public class Settings {
public void setProxyUser(String proxyUser) {
SETTINGS.setProxyUser(proxyUser);
save();
}
public String getProxyPass() {
@@ -218,15 +225,22 @@ public class Settings {
public void setProxyPass(String proxyPass) {
SETTINGS.setProxyPass(proxyPass);
save();
}
public boolean hasProxy() { return SETTINGS.isHasProxy(); }
public void setHasProxy(boolean hasProxy) { SETTINGS.setHasProxy(hasProxy); }
public void setHasProxy(boolean hasProxy) {
SETTINGS.setHasProxy(hasProxy);
save();
}
public boolean hasProxyAuth() { return SETTINGS.isHasProxyAuth(); }
public void setHasProxyAuth(boolean hasProxyAuth) { SETTINGS.setHasProxyAuth(hasProxyAuth); }
public void setHasProxyAuth(boolean hasProxyAuth) {
SETTINGS.setHasProxyAuth(hasProxyAuth);
save();
}
private void loadProxy() {
String host = getProxyHost();
@@ -261,6 +275,7 @@ public class Settings {
public void setFont(Font font) {
SETTINGS.setFontFamily(font.getFamily());
SETTINGS.setFontSize(font.getSize());
save();
}
public int getLogLines() {
@@ -269,6 +284,7 @@ public class Settings {
public void setLogLines(int logLines) {
SETTINGS.setLogLines(logLines);
save();
}
/****************************************
@@ -296,8 +312,10 @@ public class Settings {
}
private void checkAuthlibInjectorServerURLs() {
if (SETTINGS.getAuthlibInjectorServerURLs() == null)
if (SETTINGS.getAuthlibInjectorServerURLs() == null) {
SETTINGS.setAuthlibInjectorServerURLs(new HashSet<>());
save();
}
}
private void checkAuthlibInjectorAccounts() {
@@ -323,6 +341,7 @@ public class Settings {
if (index == -1)
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
SETTINGS.setDownloadType(index);
save();
}
/****************************************
@@ -352,6 +371,7 @@ public class Settings {
super.invalidated();
SETTINGS.setSelectedAccount(getValue() == null ? "" : Accounts.getAccountId(getValue()));
save();
}
};
@@ -404,6 +424,7 @@ public class Settings {
super.invalidated();
SETTINGS.setBackgroundImage(get());
save();
}
};
@@ -425,6 +446,7 @@ public class Settings {
super.invalidated();
SETTINGS.setBackgroundImageType(get().ordinal());
save();
}
};
@@ -450,6 +472,7 @@ public class Settings {
super.invalidated();
SETTINGS.setTheme(get().getName().toLowerCase());
save();
}
};
@@ -473,7 +496,10 @@ public class Settings {
checkProfileMap();
if (!hasProfile(SETTINGS.getSelectedProfile())) {
getProfileMap().keySet().stream().findFirst().ifPresent(SETTINGS::setSelectedProfile);
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
SETTINGS.setSelectedProfile(selectedProfile);
save();
});
Schedulers.computation().schedule(this::onProfileChanged);
}
return getProfile(SETTINGS.getSelectedProfile());
@@ -482,6 +508,7 @@ public class Settings {
public void setSelectedProfile(Profile selectedProfile) {
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.getSelectedProfile())) {
SETTINGS.setSelectedProfile(selectedProfile.getName());
save();
Schedulers.computation().schedule(this::onProfileChanged);
}
}
@@ -555,4 +582,8 @@ public class Settings {
public void onAccountLoading() {
EventBus.EVENT_BUS.fireEvent(new AccountLoadingEvent(this, getAccounts()));
}
public Config getRawConfig() {
return SETTINGS.clone();
}
}
@@ -22,12 +22,15 @@ import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXProgressBar;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.AddAccountPane;
import org.jackhuang.hmcl.ui.FXUtils;
import java.util.function.Consumer;
@@ -35,18 +38,19 @@ public class AccountLoginPane extends StackPane {
private final Account oldAccount;
private final Consumer<AuthInfo> success;
private final Runnable failed;
private final Consumer<Region> closeConsumer;
@FXML
private Label lblUsername;
@FXML private JFXPasswordField txtPassword;
@FXML private Label lblCreationWarning;
@FXML private JFXProgressBar progressBar;
private JFXDialog dialog;
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
public AccountLoginPane(Account oldAccount, Consumer<Region> closeConsumer, Consumer<AuthInfo> success, Runnable failed) {
this.oldAccount = oldAccount;
this.success = success;
this.failed = failed;
this.closeConsumer = closeConsumer;
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
@@ -69,9 +73,9 @@ public class AccountLoginPane extends StackPane {
Object account = variable.get("login");
if (account instanceof AuthInfo) {
success.accept(((AuthInfo) account));
dialog.close();
closeConsumer.accept(this);
} else if (account instanceof NoSelectedCharacterException) {
dialog.close();
closeConsumer.accept(this);
} else if (account instanceof Exception) {
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
}
@@ -83,10 +87,6 @@ public class AccountLoginPane extends StackPane {
@FXML
private void onCancel() {
failed.run();
dialog.close();
}
public void setDialog(JFXDialog dialog) {
this.dialog = dialog;
closeConsumer.accept(this);
}
}
@@ -18,10 +18,14 @@
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXProgressBar;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
@@ -37,9 +41,12 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import java.util.Optional;
public class AccountPage extends StackPane implements DecoratorPage {
private final StringProperty title;
private final ObjectProperty<Runnable> onDelete = new SimpleObjectProperty<>(this, "onDelete");
private final VersionListItem item;
private final Account account;
@FXML
@@ -60,9 +67,13 @@ public class AccountPage extends StackPane implements DecoratorPage {
private JFXButton btnDelete;
@FXML
private JFXButton btnRefresh;
@FXML
private JFXProgressBar progressBar;
public AccountPage(Account account) {
public AccountPage(Account account, VersionListItem item) {
this.account = account;
this.item = item;
title = new SimpleStringProperty(this, "title", Launcher.i18n("account") + " - " + account.getCharacter());
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
@@ -95,13 +106,23 @@ public class AccountPage extends StackPane implements DecoratorPage {
@FXML
private void onDelete() {
Settings.INSTANCE.deleteAccount(account);
Controllers.navigate(null);
Optional.ofNullable(onDelete.get()).ifPresent(Runnable::run);
}
@FXML
private void onRefresh() {
if (account instanceof YggdrasilAccount)
AccountHelper.refreshSkinAsync((YggdrasilAccount) account).start();
if (account instanceof YggdrasilAccount) {
progressBar.setVisible(true);
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
progressBar.setVisible(false);
if (isDependentsSucceeded) {
Image image = AccountHelper.getSkin((YggdrasilAccount) account, 4);
item.setImage(image, AccountHelper.getViewport(4));
}
}).start();
}
}
public String getTitle() {
@@ -116,4 +137,16 @@ public class AccountPage extends StackPane implements DecoratorPage {
public void setTitle(String title) {
this.title.set(title);
}
public Runnable getOnDelete() {
return onDelete.get();
}
public ObjectProperty<Runnable> onDeleteProperty() {
return onDelete;
}
public void setOnDelete(Runnable onDelete) {
this.onDelete.set(onDelete);
}
}
@@ -28,6 +28,7 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.*;
@@ -49,6 +50,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -65,9 +67,10 @@ public class AddAccountPane extends StackPane {
@FXML private Label lblAddInjectorServer;
@FXML private Hyperlink linkAddInjectorServer;
@FXML private JFXDialogLayout layout;
private final Runnable finalization;
@FXML private JFXButton btnAccept;
private final Consumer<Region> finalization;
public AddAccountPane(Runnable finalization) {
public AddAccountPane(Consumer<Region> finalization) {
this.finalization = finalization;
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
@@ -81,6 +84,7 @@ public class AddAccountPane extends StackPane {
cboServers.setVisible(newValue.intValue() == 2);
linkAddInjectorServer.setVisible(newValue.intValue() == 2);
lblAddInjectorServer.setVisible(newValue.intValue() == 2);
validateAcceptButton();
});
cboType.getSelectionModel().select(0);
@@ -92,6 +96,12 @@ public class AddAccountPane extends StackPane {
txtUsername.setOnAction(e -> onCreationAccept());
txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
txtUsername.textProperty().addListener(it -> validateAcceptButton());
txtPassword.textProperty().addListener(it -> validateAcceptButton());
}
private void validateAcceptButton() {
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
}
private void loadServers() {
@@ -133,10 +143,10 @@ public class AddAccountPane extends StackPane {
}).finalized(Schedulers.javafx(), variables -> {
Settings.INSTANCE.addAccount(variables.get("create_account"));
progressBar.setVisible(false);
finalization.run();
finalization.accept(this);
}, exception -> {
if (exception instanceof NoSelectedCharacterException) {
finalization.run();
finalization.accept(this);
} else {
lblCreationWarning.setText(accountException(exception));
}
@@ -146,12 +156,12 @@ public class AddAccountPane extends StackPane {
@FXML
private void onCreationCancel() {
finalization.run();
finalization.accept(this);
}
@FXML
private void onAddInjecterServer() {
finalization.run();
finalization.accept(this);
Controllers.navigate(Controllers.getServersPage());
}
@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXDialog;
import javafx.scene.Node;
import javafx.scene.Scene;
@@ -32,6 +33,7 @@ import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.JavaVersion;
import org.jackhuang.hmcl.util.Lang;
import java.util.function.Consumer;
@@ -115,14 +117,8 @@ public final class Controllers {
stage.setTitle(Launcher.TITLE);
}
public static Region getDialogContent() {
return decorator.getDialog().getContent();
}
public static JFXDialog dialog(Region content) {
// TODO: temp fix
decorator.showDialog(new Region());
return decorator.showDialog(content);
public static void dialog(Region content) {
decorator.showDialog(content);
}
public static void dialog(String text) {
@@ -138,28 +134,33 @@ public final class Controllers {
}
public static void dialog(String text, String title, int type, Runnable onAccept) {
dialog(new MessageDialogPane(text, title, decorator.getDialog(), type, onAccept));
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, type, onAccept));
}
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
dialog(new MessageDialogPane(text, title, decorator.getDialog(), onAccept, onCancel));
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, onAccept, onCancel));
}
public static void inputDialog(String text, Consumer<String> onResult) {
dialog(new InputDialogPane(text, decorator.getDialog(), onResult));
dialog(new InputDialogPane(text, Controllers::closeDialog, onResult));
}
public static void taskDialog(TaskExecutor executor, String title, String subtitle, Runnable onCancel) {
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
return taskDialog(executor, title, subtitle, null);
}
public static Region taskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
pane.setTitle(title);
pane.setSubtitle(subtitle);
pane.setExecutor(executor);
executor.start();
dialog(pane);
return pane;
}
public static void closeDialog() {
decorator.getDialog().close();
public static void closeDialog(Region content) {
decorator.closeDialog(content);
}
public static void navigate(Node node) {
@@ -53,6 +53,7 @@ import org.jackhuang.hmcl.ui.animation.AnimationProducer;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
import org.jackhuang.hmcl.ui.wizard.*;
import org.jackhuang.hmcl.util.FileUtils;
@@ -82,11 +83,13 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
private final boolean max, min;
private final WizardController wizardController = new WizardController(this);
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
private final JFXDialog dialog;
private double xOffset, yOffset, newX, newY, initX, initY;
private boolean allowMove, isDragging, dialogShown, maximized;
private boolean allowMove, isDragging, maximized;
private BoundingBox originalBox, maximizedBox;
private final TransitionHandler animationHandler;
private final StackContainerPane dialogPane = new StackContainerPane();
@FXML
private StackPane contentPlaceHolder;
@@ -119,8 +122,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
@FXML
private JFXHamburger titleBurger;
@FXML
private JFXDialog dialog;
@FXML
private JFXButton btnMin;
@FXML
private JFXButton btnMax;
@@ -165,9 +166,11 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
btnMax.fire();
});
dialog = new JFXDialog();
dialog.setOverlayClose(false);
drawerWrapper.getChildren().add(0, dialog);
dialog.setDialogContainer(drawerWrapper);
dialog.setOnDialogClosed(e -> dialogShown = false);
dialog.setOnDialogOpened(e -> dialogShown = true);
dialog.setContent(dialogPane);
if (!min) buttonsContainer.getChildren().remove(btnMin);
if (!max) buttonsContainer.getChildren().remove(btnMax);
@@ -188,14 +191,17 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
}
private void loadBackground() {
Image background;
try {
Image background;
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
else
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
else
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
} catch (IllegalArgumentException ignore) {
}
}
private static Image searchBackgroundImage(Image def, String customPath) {
@@ -511,6 +517,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
}
public void showPage(Node content) {
FXUtils.checkFxUserThread();
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
if (content != null)
contentPlaceHolder.getStyleClass().add("gray-background");
@@ -531,11 +539,23 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
}
}
public JFXDialog showDialog(Region content) {
dialog.setContent(content);
if (!dialogShown)
public void showDialog(Node node) {
FXUtils.checkFxUserThread();
if (dialogPane.isEmpty())
dialog.show();
return dialog;
dialogPane.push(node);
}
public void closeDialog(Node node) {
FXUtils.checkFxUserThread();
dialogPane.pop(node);
if (dialogPane.getChildren().isEmpty())
Platform.runLater(() -> {
if (dialogPane.getChildren().isEmpty())
dialog.close();
});
}
public void startWizard(WizardProvider wizardProvider) {
@@ -543,6 +563,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
}
public void startWizard(WizardProvider wizardProvider, String category) {
FXUtils.checkFxUserThread();
this.category = category;
wizardController.setProvider(wizardProvider);
wizardController.onStart();
@@ -624,10 +646,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
return wizardController;
}
public JFXDialog getDialog() {
return dialog;
}
public AdvancedListBox getLeftPane() {
return leftPane;
}
@@ -34,15 +34,11 @@ public final class DialogController {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
JFXUtilities.runInFX(() -> {
AccountLoginPane pane = new AccountLoginPane(account, it -> {
AccountLoginPane pane = new AccountLoginPane(account, Controllers::closeDialog, it -> {
res.set(it);
latch.countDown();
Controllers.closeDialog();
}, () -> {
latch.countDown();
Controllers.closeDialog();
});
pane.setDialog(Controllers.dialog(pane));
}, latch::countDown);
Controllers.dialog(pane);
});
latch.await();
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
@@ -23,6 +23,7 @@ import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@@ -48,7 +49,10 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.ReflectionHelper.call;
@@ -58,6 +62,13 @@ public final class FXUtils {
private FXUtils() {
}
public static void checkFxUserThread() {
if (!Platform.isFxApplicationThread()) {
throw new IllegalStateException("Not on FX application thread; currentThread = "
+ Thread.currentThread().getName());
}
}
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
value.addListener((a, b, c) -> consumer.accept(c));
}
@@ -368,6 +379,31 @@ public final class FXUtils {
});
}
public static <T> T runInUIThread(Supplier<T> supplier) {
if (javafx.application.Platform.isFxApplicationThread()) {
return supplier.get();
} else {
CountDownLatch doneLatch = new CountDownLatch(1);
AtomicReference<T> reference = new AtomicReference<>();
Platform.runLater(() -> {
try {
reference.set(supplier.get());
} finally {
doneLatch.countDown();
}
});
try {
doneLatch.await();
} catch (InterruptedException var3) {
Thread.currentThread().interrupt();
}
return reference.get();
}
}
public static final Image DEFAULT_ICON = new Image("/assets/img/icon.png");
public static final Interpolator SINE = new Interpolator() {
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.game.GameVersion;
@@ -89,7 +90,7 @@ public class InstallerController {
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
if (!gameVersion.isPresent())
Controllers.dialog("version.cannot_read");
Controllers.dialog(Launcher.i18n("version.cannot_read"));
else
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
}
@@ -25,6 +25,7 @@ import javafx.scene.Node;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.Launcher;
@@ -41,6 +42,7 @@ import org.jackhuang.hmcl.mod.UnsupportedModpackException;
import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.ClassTitle;
import org.jackhuang.hmcl.ui.construct.IconedItem;
@@ -50,6 +52,7 @@ import org.jackhuang.hmcl.util.Lang;
import java.io.File;
import java.util.LinkedList;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
public final class LeftPaneController {
private final AdvancedListBox leftPane;
@@ -148,8 +151,9 @@ public final class LeftPaneController {
VersionListItem item = new VersionListItem(account.getCharacter(), accountType(account));
RipplerContainer ripplerContainer = new RipplerContainer(item);
item.setOnSettingsButtonClicked(e -> {
AccountPage accountPage = new AccountPage(account);
AccountPage accountPage = new AccountPage(account, item);
JFXPopup popup = new JFXPopup(accountPage);
accountPage.setOnDelete(popup::hide);
popup.show((Node) e.getSource(), JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, e.getX(), e.getY());
});
ripplerContainer.setOnMouseClicked(e -> {
@@ -191,10 +195,10 @@ public final class LeftPaneController {
}
private boolean checkedModpack = false;
private static boolean showNewAccount = true;
private void onRefreshedVersions(RefreshedVersionsEvent event) {
JFXUtilities.runInFX(() -> {
boolean flag = true;
HMCLGameRepository repository = (HMCLGameRepository) event.getSource();
if (!checkedModpack) {
checkedModpack = true;
@@ -203,22 +207,26 @@ public final class LeftPaneController {
File modpackFile = new File("modpack.zip").getAbsoluteFile();
if (modpackFile.exists()) {
try {
AtomicReference<Region> region = new AtomicReference<>();
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
Controllers.taskDialog(ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), () -> {
Controllers.closeDialog();
checkAccount();
})).executor(),
Launcher.i18n("modpack.installing"), "", null);
flag = false;
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), () -> {
Controllers.closeDialog(region.get());
checkAccount();
})).executor();
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.installing"), ""));
executor.start();
showNewAccount = false;
} catch (UnsupportedModpackException ignore) {
}
}
}
}
if (flag)
if (showNewAccount) {
showNewAccount = false;
checkAccount();
}
});
}
@@ -25,6 +25,7 @@ import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Launcher;
@@ -53,6 +54,10 @@ import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
public final class MainPage extends StackPane implements DecoratorPage {
@@ -94,6 +99,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
FXUtils.installTooltip(btnRefresh, Launcher.i18n("button.refresh"));
}
private String modifyVersion(String gameVersion, String version) {
return removeSuffix(removePrefix(removeSuffix(removePrefix(version.replace(gameVersion, "").trim(), "-"), "-"), "_"), "_");
}
private Node buildNode(HMCLGameRepository repository, Version version, String game) {
Profile profile = repository.getProfile();
String id = version.getId();
@@ -105,13 +114,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
StringBuilder libraries = new StringBuilder();
for (Library library : version.getLibraries()) {
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
libraries.append(Launcher.i18n("install.installer.forge")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)forge", "").replace(game, "").trim(), "-"), "-")).append("\n");
libraries.append(Launcher.i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
}
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)liteloader", "").replace(game, "").trim(), "-"), "-")).append("\n");
libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
}
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)optifine", "").replace(game, "").trim(), "-"), "-")).append("\n");
libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
}
}
@@ -127,7 +136,8 @@ public final class MainPage extends StackPane implements DecoratorPage {
Controllers.dialog(Launcher.i18n("login.empty_username"));
else {
FileChooser chooser = new FileChooser();
chooser.setInitialDirectory(repository.getRunDirectory(id));
if (repository.getRunDirectory(id).isDirectory())
chooser.setInitialDirectory(repository.getRunDirectory(id));
chooser.setTitle(Launcher.i18n("version.launch_script.save"));
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
? new FileChooser.ExtensionFilter(Launcher.i18n("extension.bat"), "*.bat")
@@ -147,19 +157,20 @@ public final class MainPage extends StackPane implements DecoratorPage {
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(null);
AtomicReference<Region> region = new AtomicReference<>();
try {
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
.then(Task.of(Schedulers.javafx(), Controllers::closeDialog)).executor();
pane.setExecutor(executor);
pane.setTitle(Launcher.i18n("modpack.update"));
.then(Task.of(Schedulers.javafx(), () -> Controllers.closeDialog(region.get()))).executor();
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.update"), ""));
executor.start();
Controllers.dialog(pane);
} catch (UnsupportedModpackException e) {
Controllers.closeDialog(region.get());
Controllers.dialog(Launcher.i18n("modpack.unsupported"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (MismatchedModpackTypeException e) {
Controllers.closeDialog(region.get());
Controllers.dialog(Launcher.i18n("modpack.mismatched_type"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (IOException e) {
Controllers.closeDialog(region.get());
Controllers.dialog(Launcher.i18n("modpack.invalid"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
}
}
@@ -192,7 +203,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
FXUtils.openFolder(repository.getRunDirectory(id));
break;
default:
throw new Error();
break;
}});
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
@@ -22,14 +22,13 @@ import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
import java.util.function.Consumer;
public class InputDialogPane extends StackPane {
private final String text;
private final JFXDialog dialog;
@FXML
private JFXButton acceptButton;
@@ -40,16 +39,13 @@ public class InputDialogPane extends StackPane {
@FXML
private Label content;
public InputDialogPane(String text, JFXDialog dialog, Consumer<String> onResult) {
this.text = text;
this.dialog = dialog;
public InputDialogPane(String text, Consumer<Region> closeConsumer, Consumer<String> onResult) {
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
content.setText(text);
cancelButton.setOnMouseClicked(e -> dialog.close());
cancelButton.setOnMouseClicked(e -> closeConsumer.accept(this));
acceptButton.setOnMouseClicked(e -> {
onResult.accept(textField.getText());
dialog.close();
closeConsumer.accept(this);
});
}
}
@@ -17,10 +17,12 @@
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextInputDialog;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.ui.FXUtils;
import javax.swing.*;
import java.util.Optional;
@@ -123,30 +125,32 @@ public final class MessageBox {
}
public static int confirm(String message, String title, int option) {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(title);
alert.setHeaderText(title);
alert.setContentText(message);
switch (option) {
case YES_NO_OPTION:
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
break;
case YES_NO_CANCEL_OPTION:
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
break;
case OK_CANCEL_OPTION:
alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
break;
default:
throw new IllegalArgumentException("Unrecognized message box option " + option);
}
Optional<ButtonType> buttonType = alert.showAndWait();
if (!buttonType.isPresent()) return CLOSED_OPTION;
else if (buttonType.get() == ButtonType.OK) return OK_OPTION;
else if (buttonType.get() == ButtonType.YES) return YES_OPTION;
else if (buttonType.get() == ButtonType.NO) return NO_OPTION;
else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION;
else throw new IllegalStateException("Unrecognized button type:" + buttonType.get());
return FXUtils.runInUIThread(() -> {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle(title);
alert.setHeaderText(title);
alert.setContentText(message);
switch (option) {
case YES_NO_OPTION:
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO);
break;
case YES_NO_CANCEL_OPTION:
alert.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
break;
case OK_CANCEL_OPTION:
alert.getButtonTypes().setAll(ButtonType.OK, ButtonType.CANCEL);
break;
default:
throw new IllegalArgumentException("Unrecognized message box option " + option);
}
Optional<ButtonType> buttonType = alert.showAndWait();
if (!buttonType.isPresent()) return CLOSED_OPTION;
else if (buttonType.get() == ButtonType.OK) return OK_OPTION;
else if (buttonType.get() == ButtonType.YES) return YES_OPTION;
else if (buttonType.get() == ButtonType.NO) return NO_OPTION;
else if (buttonType.get() == ButtonType.CANCEL) return CANCEL_OPTION;
else throw new IllegalStateException("Unexpected button type:" + buttonType.get());
});
}
public static Optional<String> input(String message) {
@@ -22,6 +22,7 @@ import com.jfoenix.controls.JFXDialog;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Theme;
@@ -29,6 +30,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import java.util.Optional;
import java.util.function.Consumer;
public final class MessageDialogPane extends StackPane {
private boolean closingDialog = true;
@@ -46,7 +48,7 @@ public final class MessageDialogPane extends StackPane {
@FXML
private HBox actions;
public MessageDialogPane(String text, String title, JFXDialog dialog, int type, Runnable onAccept) {
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, int type, Runnable onAccept) {
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
if (title != null)
@@ -54,8 +56,7 @@ public final class MessageDialogPane extends StackPane {
content.setText(text);
acceptButton.setOnMouseClicked(e -> {
if (closingDialog)
dialog.close();
closeConsumer.accept(MessageDialogPane.this);
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
});
@@ -82,13 +83,12 @@ public final class MessageDialogPane extends StackPane {
}
}
public MessageDialogPane(String text, String title, JFXDialog dialog, Runnable onAccept, Runnable onCancel) {
this(text, title, dialog, MessageBox.QUESTION_MESSAGE, onAccept);
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, Runnable onAccept, Runnable onCancel) {
this(text, title, closeConsumer, MessageBox.QUESTION_MESSAGE, onAccept);
cancelButton.setVisible(true);
cancelButton.setOnMouseClicked(e -> {
if (closingDialog)
dialog.close();
closeConsumer.accept(MessageDialogPane.this);
Optional.ofNullable(onCancel).ifPresent(Runnable::run);
});
@@ -0,0 +1,49 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.util.Logging;
import java.util.Stack;
public class StackContainerPane extends StackPane {
private final Stack<Node> stack = new Stack<>();
public void push(Node node) {
stack.push(node);
getChildren().setAll(node);
Logging.LOG.info(this + " " + stack);
}
public void pop(Node node) {
boolean flag = stack.remove(node);
if (stack.isEmpty())
getChildren().setAll();
else
getChildren().setAll(stack.peek());
Logging.LOG.info(this + " " + stack + ", removed: " + flag + ", object: " + node);
}
public boolean isEmpty() {
return stack.isEmpty();
}
}
@@ -23,15 +23,17 @@ import com.jfoenix.controls.JFXProgressBar;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.FXUtils;
import java.util.Optional;
import java.util.function.Consumer;
public class TaskExecutorDialogPane extends StackPane {
private TaskExecutor executor;
private Runnable onCancel;
private Consumer<Region> onCancel;
@FXML
private JFXProgressBar progressBar;
@@ -44,14 +46,14 @@ public class TaskExecutorDialogPane extends StackPane {
@FXML
private TaskListPane taskListPane;
public TaskExecutorDialogPane(Runnable cancel) {
public TaskExecutorDialogPane(Consumer<Region> cancel) {
FXUtils.loadFXML(this, "/assets/fxml/task-dialog.fxml");
setCancel(cancel);
btnCancel.setOnMouseClicked(e -> {
Optional.ofNullable(executor).ifPresent(TaskExecutor::cancel);
onCancel.run();
onCancel.accept(this);
});
}
@@ -93,7 +95,7 @@ public class TaskExecutorDialogPane extends StackPane {
progressBar.setProgress(progress);
}
public void setCancel(Runnable onCancel) {
public void setCancel(Consumer<Region> onCancel) {
this.onCancel = onCancel;
JFXUtilities.runInFX(() -> btnCancel.setDisable(onCancel == null));
@@ -33,8 +33,8 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
@Override
default void handleTask(Map<String, Object> settings, Task task) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(() -> {
Controllers.closeDialog();
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
Controllers.closeDialog(it);
Controllers.navigate(null);
});
@@ -60,20 +60,24 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
TaskExecutor executor = task.executor(new TaskListener() {
@Override
public void onStop(boolean success, TaskExecutor executor) {
if (success) {
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
JFXUtilities.runInFX(() -> Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
else if (!settings.containsKey("forbid_success_message"))
JFXUtilities.runInFX(() -> Controllers.dialog(Launcher.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null)));
} else {
if (executor.getLastException() == null)
return;
String appendix = StringUtils.getStackTrace(executor.getLastException());
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
else if (!settings.containsKey("forbid_failure_message"))
JFXUtilities.runInFX(() -> Controllers.dialog(appendix, Launcher.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null)));
}
JFXUtilities.runInFX(() -> {
Controllers.closeDialog(pane);
if (success) {
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
else if (!settings.containsKey("forbid_success_message"))
Controllers.dialog(Launcher.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
} else {
if (executor.getLastException() == null)
return;
String appendix = StringUtils.getStackTrace(executor.getLastException());
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
else if (!settings.containsKey("forbid_failure_message"))
Controllers.dialog(appendix, Launcher.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
}
});
}
});
pane.setExecutor(executor);
@@ -17,10 +17,6 @@ public class URLValidator extends ValidatorBase {
super();
}
public URLValidator(@NamedArg("message") String message) {
super(message);
}
public ObservableList<String> getProtocols() {
return protocols;
}
@@ -89,10 +89,10 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
chkSnapshot.selectedProperty().addListener(listener);
chkOld.selectedProperty().addListener(listener);
list.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
if (newValue == null)
list.setOnMouseClicked(e -> {
if (list.getSelectionModel().getSelectedIndex() < 0)
return;
controller.getSettings().put(libraryId, newValue.getRemoteVersion());
controller.getSettings().put(libraryId, list.getSelectionModel().getSelectedItem().getRemoteVersion());
callback.run();
});
refresh();
@@ -18,14 +18,23 @@
package org.jackhuang.hmcl.ui.export;
import javafx.scene.Node;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.game.HMCLModpackExportTask;
import org.jackhuang.hmcl.game.HMCLModpackManager;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.setting.Config;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.ZipEngine;
import java.io.File;
import java.nio.file.Files;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -46,16 +55,73 @@ public final class ExportWizardProvider implements WizardProvider {
public Object finish(Map<String, Object> settings) {
@SuppressWarnings("unchecked")
List<String> whitelist = (List<String>) settings.get(ModpackFileSelectionPage.MODPACK_FILE_SELECTION);
List<File> launcherJar = Launcher.getCurrentJarFiles();
boolean includeLauncher = (Boolean) settings.get(ModpackInfoPage.MODPACK_INCLUDE_LAUNCHER) && launcherJar != null;
return new HMCLModpackExportTask(profile.getRepository(), version, whitelist,
new Modpack(
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
null,
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
null
), (File) settings.get(ModpackInfoPage.MODPACK_FILE));
return new Task() {
Task dependency = null;
@Override
public void execute() throws Exception {
File modpackFile = (File) settings.get(ModpackInfoPage.MODPACK_FILE);
File tempModpack = includeLauncher ? Files.createTempFile("hmcl", ".zip").toFile() : modpackFile;
dependency = new HMCLModpackExportTask(profile.getRepository(), version, whitelist,
new Modpack(
(String) settings.get(ModpackInfoPage.MODPACK_NAME),
(String) settings.get(ModpackInfoPage.MODPACK_AUTHOR),
(String) settings.get(ModpackInfoPage.MODPACK_VERSION),
null,
(String) settings.get(ModpackInfoPage.MODPACK_DESCRIPTION),
null
), tempModpack);
if (includeLauncher) {
dependency = dependency.then(Task.of(() -> {
boolean flag = true;
try (ZipEngine zip = new ZipEngine(modpackFile)) {
Config config = Settings.INSTANCE.getRawConfig();
config.setHasProxy(false);
config.setSelectedProfile("");
config.setCommonDirectory(null);
config.setFontFamily("Consolas");
config.setFontSize(12);
config.setJava(null);
config.setLocalization(null);
config.setAccounts(null);
config.setSelectedAccount("");
config.setLogLines(100);
config.setConfigurations(null);
zip.putTextFile(Settings.GSON.toJson(config), Settings.SETTINGS_FILE_NAME);
zip.putFile(tempModpack, "modpack.zip");
File bg = new File("bg").getAbsoluteFile();
if (bg.isDirectory())
zip.putDirectory(bg);
File background_png = new File("background.png").getAbsoluteFile();
if (background_png.isFile())
zip.putFile(background_png, "background.png");
File background_jpg = new File("background.jpg").getAbsoluteFile();
if (background_jpg.isFile())
zip.putFile(background_jpg, "background.jpg");
for (File jar : launcherJar)
zip.putFile(jar, jar.getName());
}
}));
}
}
@Override
public Collection<? extends Task> getDependencies() {
return Collections.singleton(dependency);
}
};
}
@Override
@@ -66,6 +66,9 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
}
private CheckBoxTreeItem<String> getTreeItem(File file, String basePath) {
if (!file.exists())
return null;
ModAdviser.ModSuggestion state = ModAdviser.ModSuggestion.SUGGESTED;
if (basePath.length() > "minecraft/".length()) {
state = adviser.advise(StringUtils.substringAfter(basePath, "minecraft/") + (file.isDirectory() ? "/" : ""), file.isDirectory());
@@ -33,8 +33,10 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.FileUtils;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -45,12 +47,16 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
@FXML
private JFXTextField txtModpackName;
@FXML
private JFXTextField txtModpackAuthor;@FXML
private JFXTextField txtModpackVersion;@FXML
private JFXTextField txtModpackAuthor;
@FXML
private JFXTextField txtModpackVersion;
@FXML
private JFXTextArea txtModpackDescription;
@FXML
private JFXToggleButton chkIncludeLauncher;@FXML
private JFXButton btnNext;@FXML
private JFXToggleButton chkIncludeLauncher;
@FXML
private JFXButton btnNext;
@FXML
private ScrollPane scroll;
public ModpackInfoPage(WizardController controller, String version) {
@@ -63,6 +69,10 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
txtModpackVersion.textProperty().addListener(e -> checkValidation());
txtModpackAuthor.setText(Optional.ofNullable(Settings.INSTANCE.getSelectedAccount()).map(Account::getUsername).orElse(""));
lblVersionName.setText(version);
List<File> launcherJar = Launcher.getCurrentJarFiles();
if (launcherJar == null)
chkIncludeLauncher.setDisable(true);
}
private void checkValidation() {
@@ -19,10 +19,13 @@ package org.jackhuang.hmcl.upgrade;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.*;
@@ -36,6 +39,7 @@ import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
@@ -54,12 +58,19 @@ public class AppDataUpgrader extends IUpgrader {
if (mainClass != null) {
ArrayList<String> al = new ArrayList<>(args);
al.add("--noupdate");
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
new URLClassLoader(new URL[]{jar.toURI().toURL()},
ClassLoader.getSystemClassLoader().getParent()).loadClass(mainClass)
.getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
return null;
});
ClassLoader pre = Thread.currentThread().getContextClassLoader();
try {
AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
Logging.stop();
ClassLoader now = new URLClassLoader(new URL[]{jar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
Thread.currentThread().setContextClassLoader(now);
now.loadClass(mainClass).getMethod("main", String[].class).invoke(null, new Object[]{al.toArray(new String[0])});
return null;
});
} finally {
Logging.start();
Thread.currentThread().setContextClassLoader(pre);
}
return true;
}
}
@@ -96,10 +107,10 @@ public class AppDataUpgrader extends IUpgrader {
if (!(ver instanceof IntVersionNumber))
return;
IntVersionNumber version = (IntVersionNumber) ver;
checker.requestDownloadLink().then(Task.of(Schedulers.javafx(), variables -> {
checker.requestDownloadLink().then(Task.of(variables -> {
Map<String, String> map = variables.get(UpdateChecker.REQUEST_DOWNLOAD_LINK_ID);
if (MessageBox.confirm(Launcher.i18n("update.newest_version") + version.toString() + "\n"
if (MessageBox.confirm(Launcher.i18n("update.newest_version", version.toString()) + "\n"
+ Launcher.i18n("update.should_open_link"),
MessageBox.YES_NO_OPTION) == MessageBox.YES_OPTION)
if (map != null && map.containsKey("jar") && !StringUtils.isBlank(map.get("jar")))
@@ -107,13 +118,16 @@ public class AppDataUpgrader extends IUpgrader {
String hash = null;
if (map.containsKey("jarsha1"))
hash = map.get("jarsha1");
Controllers.dialog(Launcher.i18n("message.downloading"));
if (new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash).test()) {
Task task = new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash);
TaskExecutor executor = task.executor();
AtomicReference<Region> region = new AtomicReference<>();
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
if (executor.test()) {
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
Controllers.closeDialog();
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
@@ -122,13 +136,16 @@ public class AppDataUpgrader extends IUpgrader {
String hash = null;
if (map.containsKey("packsha1"))
hash = map.get("packsha1");
Controllers.dialog(Launcher.i18n("message.downloading"));
if (new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash).test()) {
Task task = new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash);
TaskExecutor executor = task.executor();
AtomicReference<Region> region = new AtomicReference<>();
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
if (executor.test()) {
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
Controllers.closeDialog();
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
@@ -139,8 +156,6 @@ public class AppDataUpgrader extends IUpgrader {
url = map.get(OperatingSystem.CURRENT_OS.getCheckedName());
else if (map.containsKey(OperatingSystem.UNKNOWN.getCheckedName()))
url = map.get(OperatingSystem.UNKNOWN.getCheckedName());
if (url == null)
url = Launcher.PUBLISH;
try {
java.awt.Desktop.getDesktop().browse(new URI(url));
} catch (URISyntaxException | IOException e) {
@@ -17,8 +17,12 @@
*/
package org.jackhuang.hmcl.upgrade;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.util.Charsets;
import org.jackhuang.hmcl.util.Logging;
@@ -28,6 +32,7 @@ import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
/**
@@ -52,7 +57,11 @@ public class NewFileUpgrader extends IUpgrader {
if (url == null) return;
File newf = new File(url.getFile());
Controllers.dialog(Launcher.i18n("message.downloading"));
if (new FileDownloadTask(url, newf).test()) {
Task task = new FileDownloadTask(url, newf);
TaskExecutor executor = task.executor();
AtomicReference<Region> region = new AtomicReference<>();
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
if (executor.test()) {
try {
new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath())
.directory(new File("").getAbsoluteFile())
@@ -62,7 +71,7 @@ public class NewFileUpgrader extends IUpgrader {
}
System.exit(0);
}
Controllers.closeDialog();
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
}
private static String getRealPath() {
@@ -71,7 +71,7 @@ public final class UpdateChecker {
return;
if (value == null) {
versionString = NetworkUtils.doGet(NetworkUtils.toURL("http://huangyuhui.duapp.com/hmcl/update.php?version=" + Launcher.VERSION));
versionString = NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update.php?version=" + Launcher.VERSION));
value = VersionNumber.asVersion(versionString);
}
@@ -115,7 +115,7 @@ public final class UpdateChecker {
public void execute() {
if (download_link == null)
try {
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL("http://huangyuhui.duapp.com/update_link.php?type=hmcl")), Map.class);
download_link = Constants.GSON.<Map<String, String>>fromJson(NetworkUtils.doGet(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/update_link.php")), Map.class);
} catch (JsonSyntaxException | IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to get update link.", e);
}
@@ -42,6 +42,8 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
put("java.lang.NoClassDefFoundError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.VerifyError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.NoSuchMethodError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.NoSuchFieldError", Launcher.i18n("crash.NoClassDefFound"));
put("netscape.javascript.JSException", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.IncompatibleClassChangeError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.ClassFormatError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.OutOfMemoryError", "FUCKING MEMORY LIMIT!");
@@ -113,7 +115,7 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
map.put("version", Launcher.VERSION);
map.put("log", Logging.getLogs());
try {
String response = NetworkUtils.doPost(NetworkUtils.toURL("http://huangyuhui.duapp.com/hmcl/crash.php"), map);
String response = NetworkUtils.doPost(NetworkUtils.toURL("https://huangyuhui.duapp.com/hmcl/crash.php"), map);
if (StringUtils.isNotBlank(response))
Logging.LOG.log(Level.SEVERE, "Crash server response: " + response);
} catch (IOException ex) {
@@ -56,7 +56,7 @@
</body>
<actions>
<Label fx:id="lblCreationWarning"/>
<JFXButton onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
<JFXButton fx:id="btnAccept" onMouseClicked="#onCreationAccept" text="%button.ok" styleClass="dialog-accept"/>
<JFXButton onMouseClicked="#onCreationCancel" text="%button.cancel" styleClass="dialog-cancel"/>
</actions>
</JFXDialogLayout>
@@ -26,7 +26,7 @@
<actions>
<Label fx:id="lblCreationWarning" />
<JFXButton onMouseClicked="#onAccept" text="%button.ok" styleClass="dialog-accept"/>
<JFXButton onMouseClicked="#finalization" text="%button.cancel" styleClass="dialog-cancel"/>
<JFXButton onMouseClicked="#onCancel" text="%button.cancel" styleClass="dialog-cancel"/>
</actions>
</JFXDialogLayout>
<JFXProgressBar fx:id="progressBar" visible="false" StackPane.alignment="TOP_CENTER"/>
@@ -4,6 +4,7 @@
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
<?import com.jfoenix.controls.JFXProgressBar?>
<fx:root xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
type="StackPane">
@@ -77,4 +78,5 @@
prefWidth="30" prefHeight="30"/>
</right>
</BorderPane>
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" visible="false" />
</fx:root>
@@ -20,7 +20,6 @@
maxWidth="800">
<center>
<StackPane fx:id="drawerWrapper" styleClass="jfx-decorator-drawer" FXUtils.overflowHidden="true">
<JFXDialog fx:id="dialog" overlayClose="false" />
<BorderPane>
<left>
<StackPane minWidth="200" maxWidth="200" styleClass="jfx-decorator-content-container">
@@ -31,7 +31,7 @@
</right>
</BorderPane>
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%settings.choose_gamedir"/>
<FileItem fx:id="gameDir" name="%profile.instance_directory" title="%profile.instance_directory.choose"/>
</ComponentList>
</VBox>
@@ -25,7 +25,7 @@
<MultiFileItem fx:id="javaItem" title="%settings.game.java_directory" chooserTitle="%settings.game.java_directory.choose"
hasSubtitle="true" customText="%settings.custom" directory="false" />
<MultiFileItem fx:id="gameDirItem" title="%profile.instance_directory" chooserTitle="%settings.choose_gamedir"
<MultiFileItem fx:id="gameDirItem" title="%settings.game.game_directory" chooserTitle="%settings.game.game_directory.choose"
hasSubtitle="true" customText="%settings.custom" directory="true" />
<BorderPane> <!-- Max Memory -->
@@ -20,7 +20,7 @@ about.copyright.statement=Copyright (c) 2018 huangyuhui.
about.author=Author
about.author.statement=huanghongxun (MCF: klkl6523)
about.thanks_to=Thanks to
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (Default background image)\nAll contributors.
about.dependency=Dependency
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
about.claim=Statement
@@ -57,8 +57,8 @@ archive.author=Authors
archive.game_version=Game
archive.version=Version
assets.download=Download Assets
assets.download_all=Download Assets Files
assets.download=Download assets
assets.download_all=Check for the completion of assets
assets.failed=Failed to get the list, try again.
assets.failed_download=Failed to download assets, may cause no sounds and language files.
@@ -112,7 +112,7 @@ crash.minecraft=Minecraft has crashed!
crash.user_fault=Your OS or Java environment may not be properly installed resulting in crashing of this software, please check your Java Environment or your computer!
download=Download
download.BMCL=BMCLAPI (bangbang93, http://bmclapi.bangbang93.com/)
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
download.failed=Failed to download
download.failed.refresh=Unable to load version list. Click here to retry.
download.mojang=Mojang
@@ -311,6 +311,7 @@ profile=Profile
profile.default=Current directory
profile.home=User home
profile.instance_directory=Game Directory
profile.instance_directory.choose=Choose Game Directory
profile.new=New Config
profile.new_name=New Profile Name:
profile.remove=Sure to remove profile %s?
@@ -343,7 +344,6 @@ settings.advanced.server_ip=Server Host
settings.advanced.wrapper_launcher=Wrapper Launcher(i.e. optirun...)
settings.cannot_remove_default_config=Cannot remove the default configution.
settings.choose_gamedir=Choose Game Directory
settings.custom=Custom
settings.default=Default
settings.failed_load=Failed to load settings file. Remove it?
@@ -352,6 +352,8 @@ settings.game=Games
settings.game.dimension=Game Window Dimension
settings.game.exploration=Explore
settings.game.fullscreen=Fullscreen
settings.game.game_directory=Game Directory
settings.game.game_directory.choose=Choose Game Directory
settings.game.java_directory=Java Dir
settings.game.java_directory.choose=Choose Java Directory.
settings.game.management=Manage
@@ -20,7 +20,7 @@ about.copyright.statement=Copyright (c) 2018 huangyuhui.
about.author=作者
about.author.statement=huanghongxun (百度贴吧:huanghongxun20)
about.thanks_to=鸣谢
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
about.thanks_to.statement=bangbang93 (BMCLAPI, http://bmclapi2.bangbang93.com/)\ngamerteam (默认背景图)\n所有参与本项目,issue,pull requests的贡献者
about.dependency=依赖
about.dependency.statement=Gson (Google, Apache License 2.0)\nCommons Compress (Apache, Apache License 2.0)
about.claim=免责声明
@@ -58,7 +58,7 @@ archive.game_version=游戏版本
archive.version=版本
assets.download=下载资源
assets.download_all=下载资源文件
assets.download_all=检查资源文件完整性
assets.failed=获取列表失败,请刷新重试。
assets.failed_download=下载资源文件失败,可能导致没有中文和声音。
@@ -93,7 +93,7 @@ color.red=红色
color.recent=推荐
color.custom=自定义颜色
crash.NoClassDefFound=请确认HMCL本体是否完整
crash.NoClassDefFound=请确认HMCL本体是否完整,或更新您的Java。
crash.advice.ClassNotFoundException=Minecraft不完整或Mod冲突,如果有未能下载的文件请下载成功后重试或是客户端损坏请重试请重新制作客户端或下载整合包解决问题,另可尝试游戏设置->(版本)管理->删除库文件解决问题
crash.advice.ConcurrentModificationException=这可能是因为您的Java版本高于Java 1.8.0_11导致的,可以尝试卸载Java8安装Java7。
crash.advice.LWJGLException=您的电脑不正常,可能需要使用驱动精灵或其他安装器更新显卡驱动。
@@ -112,7 +112,7 @@ crash.minecraft=Minecraft崩溃了!请认真阅读建议。
crash.user_fault=您的系统或Java环境可能安装不当导致本软件崩溃,请检查您的Java环境或您的电脑!可以尝试重新安装Java。
download=下载
download.BMCL=BMCLAPI (bangbang93, http://bmclapi.bangbang93.com/)
download.BMCL=BMCLAPI (bangbang93, http://bmclapi2.bangbang93.com/)
download.failed=下载失败
download.failed.refresh=加载版本列表失败,点击此处重试。
download.mojang=官方
@@ -147,7 +147,7 @@ install.installer.forge=Forge
install.installer.game=游戏
install.installer.install=安装%s
install.installer.liteloader=LiteLoader
install.installer.not_installed=不安装%s
install.installer.not_installed=暂不安装%s,可以点击此处安装
install.installer.optifine=OptiFine
install.installer.version=%s版本
install.modpack=安装整合包
@@ -311,6 +311,7 @@ profile=配置
profile.default=当前目录
profile.home=主文件夹
profile.instance_directory=游戏路径
profile.instance_directory.choose=选择游戏路径
profile.new=新建配置
profile.new_name=新配置名:
profile.remove=真的要删除配置%s吗?
@@ -343,7 +344,6 @@ settings.advanced.server_ip=直入服务器ip地址(不必填写,启动游戏
settings.advanced.wrapper_launcher=前置指令(不必填写,如optirun)
settings.cannot_remove_default_config=不能删除默认配置
settings.choose_gamedir=选择游戏路径
settings.custom=自定义
settings.default=默认
settings.failed_load=设置文件加载失败,可能是升级了启动器或被人工修改造成错误,是否清除?
@@ -352,6 +352,8 @@ settings.game=游戏设置
settings.game.dimension=游戏窗口分辨率
settings.game.exploration=浏览
settings.game.fullscreen=全屏
settings.game.game_directory=游戏路径(版本隔离)
settings.game.game_directory.choose=选择游戏路径
settings.game.java_directory=Java路径
settings.game.java_directory.choose=选择Java路径
settings.game.management=管理
@@ -0,0 +1,61 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth;
import java.net.Proxy;
import java.util.Objects;
public final class AccountBuilder<T extends Account> {
private CharacterSelector selector = CharacterSelector.DEFAULT;
private String username;
private String password = null;
private Object additionalData = null;
private Proxy proxy = Proxy.NO_PROXY;
public AccountBuilder() {
}
public AccountBuilder setSelector(CharacterSelector selector) {
this.selector = Objects.requireNonNull(selector);
return this;
}
public AccountBuilder setUsername(String username) {
this.username = Objects.requireNonNull(username);
return this;
}
public AccountBuilder setPassword(String password) {
this.password = password;
return this;
}
public AccountBuilder setAdditionalData(Object additionalData) {
this.additionalData = additionalData;
return this;
}
public AccountBuilder setProxy(Proxy proxy) {
this.proxy = Objects.requireNonNull(proxy);
return this;
}
public T create(AccountFactory<T> factory) throws AuthenticationException {
return factory.create(selector, Objects.requireNonNull(username), password, additionalData, proxy);
}
}
@@ -20,10 +20,7 @@ package org.jackhuang.hmcl.auth.offline;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.*;
import java.util.Map;
import java.util.Objects;
@@ -66,7 +63,7 @@ public class OfflineAccount extends Account {
@Override
public AuthInfo logIn() throws AuthenticationException {
if (StringUtils.isBlank(username) || StringUtils.isBlank(uuid))
if (StringUtils.isBlank(username))
throw new AuthenticationException("Username cannot be empty");
return new AuthInfo(username, uuid, uuid);
@@ -107,6 +104,9 @@ public class OfflineAccount extends Account {
@Override
public String toString() {
return "OfflineAccount[username=" + username + ", uuid=" + uuid + "]";
return new ToStringBuilder(this)
.append("username", username)
.append("uuid", uuid)
.toString();
}
}
@@ -21,9 +21,13 @@ import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.CharacterSelector;
import org.jackhuang.hmcl.util.DigestUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.UUIDTypeAdapter;
import java.net.Proxy;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
/**
*
@@ -47,6 +51,9 @@ public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
String uuid = Lang.get(storage, "uuid", String.class)
.orElse(getUUIDFromUserName(username));
// Check if the uuid is vaild
UUIDTypeAdapter.fromString(uuid);
return new OfflineAccount(username, uuid);
}
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download;
import org.jackhuang.hmcl.download.forge.ForgeVersionList;
import org.jackhuang.hmcl.download.game.GameVersionList;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderBMCLVersionList;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderVersionList;
import org.jackhuang.hmcl.download.optifine.OptiFineBMCLVersionList;
@@ -35,27 +36,27 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
@Override
public String getLibraryBaseURL() {
return "http://bmclapi2.bangbang93.com/libraries/";
return "https://bmclapi2.bangbang93.com/libraries/";
}
@Override
public String getVersionListURL() {
return "http://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
return "https://bmclapi2.bangbang93.com/mc/game/version_manifest.json";
}
@Override
public String getVersionBaseURL() {
return "http://bmclapi2.bangbang93.com/versions/";
return "https://bmclapi2.bangbang93.com/versions/";
}
@Override
public String getAssetIndexBaseURL() {
return "http://bmclapi2.bangbang93.com/indexes/";
return "https://bmclapi2.bangbang93.com/indexes/";
}
@Override
public String getAssetBaseURL() {
return "http://bmclapi2.bangbang93.com/assets/";
return "https://bmclapi2.bangbang93.com/assets/";
}
@Override
@@ -66,7 +67,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
case "forge":
return ForgeVersionList.INSTANCE;
case "liteloader":
return LiteLoaderVersionList.INSTANCE;
return LiteLoaderBMCLVersionList.INSTANCE;
case "optifine":
return OptiFineBMCLVersionList.INSTANCE;
default:
@@ -80,7 +81,7 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
.replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com")
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
.replace("http://files.minecraftforge.net/maven", "https://bmclapi2.bangbang93.com/maven")
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven")
.replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven");
}
@@ -20,12 +20,8 @@ package org.jackhuang.hmcl.download;
public class CurseCDNDownloadProvider extends MojangDownloadProvider {
public static final CurseCDNDownloadProvider INSTANCE = new CurseCDNDownloadProvider();
protected CurseCDNDownloadProvider() {
super(false);
}
@Override
public String injectURL(String baseURL) {
return baseURL == null ? null : baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
return baseURL == null ? null : baseURL.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://ftb.cursecdn.com/FTB2/maven");
}
}
@@ -41,9 +41,12 @@ public class MaintainTask extends TaskResult<Version> {
@Override
public void execute() {
Version newVersion = version;
setResult(maintain(version));
}
public static Version maintain(Version version) {
Library forge = null, liteLoader = null, optiFine = null;
List<String> args = new ArrayList<>(StringUtils.tokenize(newVersion.getMinecraftArguments().orElse("")));
List<String> args = new ArrayList<>(StringUtils.tokenize(version.getMinecraftArguments().orElse("")));
for (Library library : version.getLibraries()) {
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge"))
@@ -84,11 +87,13 @@ public class MaintainTask extends TaskResult<Version> {
}
if ((liteLoader != null || forge != null) && optiFine != null) {
// If forge or LiteLoader installed, OptiFine Tweaker will cause crash.
// If forge or LiteLoader installed, OptiFine Forge Tweaker is needed.
removeTweakClass(args, "optifine");
args.add("--tweakClass");
args.add("optifine.OptiFineForgeTweaker");
}
setResult(newVersion.setMinecraftArguments(StringUtils.makeCommand(args)));
return version.setMinecraftArguments(StringUtils.makeCommand(args));
}
@Override
@@ -28,12 +28,6 @@ import org.jackhuang.hmcl.download.optifine.OptiFineVersionList;
*/
public class MojangDownloadProvider implements DownloadProvider {
private final boolean isChina;
public MojangDownloadProvider(boolean isChina) {
this.isChina = isChina;
}
@Override
public String getLibraryBaseURL() {
return "https://libraries.minecraft.net/";
@@ -46,17 +40,17 @@ public class MojangDownloadProvider implements DownloadProvider {
@Override
public String getVersionBaseURL() {
return "http://s3.amazonaws.com/Minecraft.Download/versions/";
return "https://s3.amazonaws.com/Minecraft.Download/versions/";
}
@Override
public String getAssetIndexBaseURL() {
return "http://s3.amazonaws.com/Minecraft.Download/indexes/";
return "https://s3.amazonaws.com/Minecraft.Download/indexes/";
}
@Override
public String getAssetBaseURL() {
return "http://resources.download.minecraft.net/";
return "https://resources.download.minecraft.net/";
}
@Override
@@ -77,16 +71,6 @@ public class MojangDownloadProvider implements DownloadProvider {
@Override
public String injectURL(String baseURL) {
if (baseURL == null)
return null;
//else if (baseURL.contains("scala-swing") || baseURL.contains("scala-xml") || baseURL.contains("scala-parser-combinators"))
// return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven");
/*else if (baseURL.contains("typesafe") || baseURL.contains("scala"))
if (isChina)
return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
else
return baseURL.replace("http://files.minecraftforge.net/maven", "http://repo1.maven.org/maven2");
*/else
return baseURL;
}
}
@@ -92,5 +92,5 @@ public final class ForgeVersionList extends VersionList<Void> {
};
}
public static final String FORGE_LIST = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/json";
public static final String FORGE_LIST = "https://files.minecraftforge.net/maven/net/minecraftforge/forge/json";
}
@@ -17,10 +17,12 @@
*/
package org.jackhuang.hmcl.download.game;
import org.jackhuang.hmcl.game.CompatibilityRule;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Platform;
import org.jackhuang.hmcl.util.SimpleMultimap;
import org.jackhuang.hmcl.util.VersionNumber;
@@ -44,7 +46,6 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
public void execute() {
List<Library> libraries = new ArrayList<>(version.getLibraries());
Map<String, VersionNumber> versionMap = new HashMap<>();
SimpleMultimap<String, Library> multimap = new SimpleMultimap<String, Library>(HashMap::new, LinkedList::new);
for (Library library : libraries) {
@@ -52,32 +53,36 @@ public class LibrariesUniqueTask extends TaskResult<Version> {
VersionNumber number = VersionNumber.asVersion(library.getVersion());
String serialized = Constants.GSON.toJson(library);
if (versionMap.containsKey(id)) {
VersionNumber otherNumber = versionMap.get(id);
if (number.compareTo(otherNumber) > 0) {
multimap.removeKey(id);
versionMap.put(id, number);
multimap.put(id, library);
} else if (number.compareTo(otherNumber) == 0) { // same library id.
boolean flag = false;
// prevent from duplicated libraries
for (Library otherLibrary : multimap.get(id))
if (library.equals(otherLibrary)) {
String otherSerialized = Constants.GSON.toJson(otherLibrary);
// A trick, the library that has more information is better, which can be
// considered whose serialized JSON text will be longer.
if (serialized.length() <= otherSerialized.length()) {
flag = true;
break;
} else {
multimap.removeValue(id, otherLibrary);
if (multimap.containsKey(id)) {
boolean hasEqualRule = false;
for (Library otherLibrary : multimap.get(id)) {
VersionNumber otherNumber = VersionNumber.asVersion(otherLibrary.getVersion());
if (CompatibilityRule.equals(library.getRules(), otherLibrary.getRules())) { // rules equal, ignore older version.
hasEqualRule = true;
if (number.compareTo(otherNumber) > 0) { // if this library is newer
multimap.removeValue(otherLibrary);
multimap.put(id, library);
break;
} else if (number.compareTo(otherNumber) == 0) { // same library id.
// prevent from duplicated libraries
if (library.equals(otherLibrary)) {
String otherSerialized = Constants.GSON.toJson(otherLibrary);
// A trick, the library that has more information is better, which can be
// considered whose serialized JSON text will be longer.
if (serialized.length() > otherSerialized.length()) {
multimap.removeValue(id, otherLibrary);
multimap.put(id, library);
break;
}
}
}
if (!flag)
multimap.put(id, library);
}
}
if (!hasEqualRule) {
multimap.put(id, library);
}
} else {
versionMap.put(id, number);
multimap.put(id, library);
}
}
@@ -0,0 +1,121 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.download.liteloader;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.task.GetTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.VersionNumber;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
/**
*
* @author huangyuhui
*/
public final class LiteLoaderBMCLVersionList extends VersionList<LiteLoaderRemoteVersionTag> {
public static final LiteLoaderBMCLVersionList INSTANCE = new LiteLoaderBMCLVersionList();
private LiteLoaderBMCLVersionList() {
}
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(LITELOADER_LIST)));
return new Task() {
@Override
public Collection<Task> getDependents() {
return Collections.singleton(task);
}
@Override
public void execute() {
lock.writeLock().lock();
try {
LiteLoaderVersionsRoot root = Constants.GSON.fromJson(task.getResult(), LiteLoaderVersionsRoot.class);
versions.clear();
for (Map.Entry<String, LiteLoaderGameVersions> entry : root.getVersions().entrySet()) {
String gameVersion = entry.getKey();
LiteLoaderGameVersions liteLoader = entry.getValue();
Optional<String> gg = VersionNumber.parseVersion(gameVersion);
if (!gg.isPresent())
continue;
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getArtifacts(), false);
doBranch(gg.get(), gameVersion, liteLoader.getRepoitory(), liteLoader.getSnapshots(), true);
}
} finally {
lock.writeLock().unlock();
}
}
private void doBranch(String key, String gameVersion, LiteLoaderRepository repository, LiteLoaderBranch branch, boolean snapshot) {
if (branch == null || repository == null)
return;
for (Map.Entry<String, LiteLoaderVersion> entry : branch.getLiteLoader().entrySet()) {
String branchName = entry.getKey();
LiteLoaderVersion v = entry.getValue();
if ("latest".equals(branchName))
continue;
String version = v.getVersion();
String url = "http://bmclapi2.bangbang93.com/liteloader/download?version=" + version;
if (snapshot) {
try {
version = version.replace("SNAPSHOT", getLatestSnapshotVersion(repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/"));
url = repository.getUrl() + "com/mumfrey/liteloader/" + v.getVersion() + "/liteloader-" + version + "-release.jar";
} catch (Exception ignore) {
}
}
versions.put(key, new LiteLoaderRemoteVersion(gameVersion,
version, downloadProvider.injectURL(url),
v.getTweakClass(), v.getLibraries()
));
}
}
};
}
public static final String LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";
private static String getLatestSnapshotVersion(String repo) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(repo + "maven-metadata.xml");
Element r = doc.getDocumentElement();
Element snapshot = (Element) r.getElementsByTagName("snapshot").item(0);
Node timestamp = snapshot.getElementsByTagName("timestamp").item(0);
Node buildNumber = snapshot.getElementsByTagName("buildNumber").item(0);
return timestamp.getTextContent() + "-" + buildNumber.getTextContent();
}
}
@@ -42,7 +42,7 @@ public final class OptiFineBMCLVersionList extends VersionList<Void> {
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi.bangbang93.com/optifine/versionlist"));
GetTask task = new GetTask(NetworkUtils.toURL("http://bmclapi2.bangbang93.com/optifine/versionlist"));
return new Task() {
@Override
public Collection<Task> getDependents() {
@@ -70,13 +70,13 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
@Override
public void execute() {
String remoteVersion = remote.getSelfVersion();
String remoteVersion = remote.getGameVersion() + "-" + remote.getSelfVersion();
Library library = new Library(
"net.optifine", "optifine", remoteVersion, null, null,
new LibrariesDownloadInfo(new LibraryDownloadInfo(
"net/optifine/optifine/" + remoteVersion + "/optifine-" + remoteVersion + ".jar",
remote.getUrl())), true
remote.getUrl()))
);
List<Library> libraries = new LinkedList<>();
@@ -99,6 +99,8 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
String minecraftArguments = version.getMinecraftArguments().orElse("");
if (!hasFMLTweaker)
minecraftArguments = minecraftArguments + " --tweakClass optifine.OptiFineTweaker";
else
minecraftArguments = minecraftArguments + " --tweakClass optifine.OptiFineForgeTweaker";
if (version.getMainClass() == null || !version.getMainClass().startsWith("net.minecraft.launchwrapper."))
libraries.add(0, new Library("net.minecraft", "launchwrapper", "1.12"));
@@ -52,7 +52,7 @@ public final class OptiFineVersionList extends VersionList<Void> {
@Override
public Task refreshAsync(DownloadProvider downloadProvider) {
GetTask task = new GetTask(NetworkUtils.toURL("http://optifine.net/downloads"));
GetTask task = new GetTask(NetworkUtils.toURL("https://optifine.net/downloads"));
return new Task() {
@Override
public Collection<Task> getDependents() {
@@ -116,6 +116,6 @@ public final class OptiFineVersionList extends VersionList<Void> {
result = m.group(1);
if (result == null)
throw new IllegalStateException("Cannot find version in " + content);
return "http://optifine.net/downloadx?f=OptiFine" + result;
return "https://optifine.net/downloadx?f=OptiFine" + result;
}
}
@@ -40,7 +40,7 @@ public class ClassicVersion extends Version {
public ClassicLibrary(String name) {
super("", "", "", null, null,
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
false, null, null, null, null);
null, null, null, null);
}
}
@@ -76,6 +76,25 @@ public final class CompatibilityRule {
return action == Action.ALLOW;
}
public static boolean equals(Collection<CompatibilityRule> rules1, Collection<CompatibilityRule> rules2) {
return Objects.hashCode(rules1) == Objects.hashCode(rules2);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CompatibilityRule that = (CompatibilityRule) o;
return action == that.action &&
Objects.equals(os, that.os) &&
Objects.equals(features, that.features);
}
@Override
public int hashCode() {
return Objects.hash(action, os, features);
}
public enum Action {
ALLOW,
DISALLOW
@@ -116,7 +116,7 @@ public final class GameVersion {
.getEntry("net/minecraft/client/Minecraft.class");
if (minecraft != null)
return getVersionOfOldMinecraft(f, minecraft);
ZipArchiveEntry main = f.getEntry("net/minecraft/client/main/Launcher.class");
ZipArchiveEntry main = f.getEntry("net/minecraft/client/main/Main.class");
ZipArchiveEntry minecraftServer = f.getEntry("net/minecraft/server/MinecraftServer.class");
if ((main != null) && (minecraftServer != null))
return getVersionOfNewMinecraft(f, minecraftServer);
@@ -45,7 +45,6 @@ public class Library implements Comparable<Library> {
private final LibrariesDownloadInfo downloads;
private final LibraryDownloadInfo download;
private final ExtractRules extract;
private final boolean lateload;
private final Map<OperatingSystem, String> natives;
private final List<CompatibilityRule> rules;
private final List<String> checksums;
@@ -55,16 +54,12 @@ public class Library implements Comparable<Library> {
public Library(String groupId, String artifactId, String version) {
this(groupId, artifactId, version, null, null, null);
}
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads) {
this(groupId, artifactId, version, classifier, url, downloads, false);
this(groupId, artifactId, version, classifier, url, downloads, null, null, null, null);
}
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload) {
this(groupId, artifactId, version, classifier, url, downloads, lateload, null, null, null, null);
}
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
this.groupId = groupId;
this.artifactId = artifactId;
this.version = version;
@@ -78,7 +73,6 @@ public class Library implements Comparable<Library> {
this.url = url;
this.downloads = downloads;
this.extract = extract;
this.lateload = lateload;
this.natives = natives;
this.rules = rules;
this.checksums = checksums;
@@ -139,14 +133,14 @@ public class Library implements Comparable<Library> {
return download;
}
public boolean isLateload() {
return lateload;
}
public List<String> getChecksums() {
return checksums;
}
public List<CompatibilityRule> getRules() {
return rules;
}
public boolean is(String groupId, String artifactId) {
return this.groupId.equals(groupId) && this.artifactId.equals(artifactId);
}
@@ -179,7 +173,7 @@ public class Library implements Comparable<Library> {
}
public Library setClassifier(String classifier) {
return new Library(groupId, artifactId, version, classifier, url, downloads, lateload, checksums, extract, natives, rules);
return new Library(groupId, artifactId, version, classifier, url, downloads, checksums, extract, natives, rules);
}
public static Library fromName(String name) {
@@ -191,7 +185,7 @@ public class Library implements Comparable<Library> {
if (arr.length != 3 && arr.length != 4)
throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier).");
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, false, checksums, extract, natives, rules);
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, checksums, extract, natives, rules);
}
public static class Serializer implements JsonDeserializer<Library>, JsonSerializer<Library> {
@@ -110,29 +110,23 @@ public class DefaultLauncher extends Launcher {
res.add("-Dfml.ignorePatchDiscrepancies=true");
}
LinkedList<File> lateload = new LinkedList<>();
StringBuilder classpath = new StringBuilder();
LinkedList<String> classpath = new LinkedList<>();
for (Library library : version.getLibraries())
if (library.appliesToCurrentEnvironment() && !library.isNative()) {
File f = repository.getLibraryFile(version, library);
if (f.exists() && f.isFile())
if (library.isLateload())
lateload.add(f);
else
classpath.append(f.getAbsolutePath()).append(OperatingSystem.PATH_SEPARATOR);
classpath.add(f.getAbsolutePath());
}
for (File library : lateload)
classpath.append(library.getAbsolutePath()).append(OperatingSystem.PATH_SEPARATOR);
File jar = repository.getVersionJar(version);
if (!jar.exists() || !jar.isFile())
throw new IOException("Minecraft jar does not exist");
classpath.append(jar.getAbsolutePath());
classpath.add(jar.getAbsolutePath());
// Provided Minecraft arguments
File gameAssets = repository.getActualAssetDirectory(version.getId(), version.getAssetIndex().getId());
Map<String, String> configuration = getConfigurations();
configuration.put("${classpath}", classpath.toString());
configuration.put("${classpath}", String.join(OperatingSystem.PATH_SEPARATOR, classpath));
configuration.put("${natives_directory}", nativeFolder.getAbsolutePath());
configuration.put("${game_assets}", gameAssets.getAbsolutePath());
configuration.put("${assets_root}", gameAssets.getAbsolutePath());
@@ -301,7 +295,7 @@ public class DefaultLauncher extends Launcher {
if (isWindows) {
writer.write("@echo off");
writer.newLine();
writer.write("set APPDATA=" + options.getGameDir().getParent());
writer.write("set APPDATA=" + options.getGameDir().getAbsoluteFile().getParent());
writer.newLine();
writer.write("cd /D %APPDATA%");
writer.newLine();
@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.task;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import java.util.concurrent.*;
@@ -115,6 +116,8 @@ public final class Schedulers {
static final Scheduler NONE = new SchedulerImpl(Constants.emptyConsumer());
public static synchronized void shutdown() {
Logging.LOG.info("Shutting down executor services.");
if (CACHED_EXECUTOR != null)
CACHED_EXECUTOR.shutdown();
@@ -205,6 +205,13 @@ public abstract class Task {
return new TaskExecutor(this);
}
public final TaskExecutor executor(boolean start) {
TaskExecutor executor = new TaskExecutor(this);
if (start)
executor.start();
return executor;
}
public final TaskExecutor executor(TaskListener taskListener) {
TaskExecutor executor = new TaskExecutor(this);
executor.addTaskListener(taskListener);
@@ -48,8 +48,8 @@ public final class Constants {
public static final Charset SYSTEM_CHARSET = Charset.forName(OperatingSystem.ENCODING);
public static final String DEFAULT_LIBRARY_URL = "https://libraries.minecraft.net/";
public static final String DEFAULT_VERSION_DOWNLOAD_URL = "http://s3.amazonaws.com/Minecraft.Download/versions/";
public static final String DEFAULT_INDEX_URL = "http://s3.amazonaws.com/Minecraft.Download/indexes/";
public static final String DEFAULT_VERSION_DOWNLOAD_URL = "https://s3.amazonaws.com/Minecraft.Download/versions/";
public static final String DEFAULT_INDEX_URL = "https://s3.amazonaws.com/Minecraft.Download/indexes/";
public static Consumer<Runnable> UI_THREAD_SCHEDULER = s -> Schedulers.computation().schedule(s::run);
@@ -43,11 +43,14 @@ public final class IntVersionNumber extends VersionNumber {
if (!(o instanceof IntVersionNumber))
return 0;
IntVersionNumber other = (IntVersionNumber) o;
int len = Math.min(this.version.size(), other.version.size());
for (int i = 0; i < len; ++i)
if (!version.get(i).equals(other.version.get(i)))
return version.get(i).compareTo(other.version.get(i));
return Integer.compare(this.version.size(), other.version.size());
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
@@ -35,6 +35,9 @@ public final class Logging {
static {
LOG = Logger.getLogger("HMCL");
}
public static void start() {
LOG.setLevel(Level.FINER);
LOG.setUseParentHandlers(false);
@@ -63,6 +66,11 @@ public final class Logging {
LOG.addHandler(streamHandler);
}
public static void stop() {
for (Handler handler : LOG.getHandlers())
LOG.removeHandler(handler);
}
public static String getLogs() {
return OUTPUT_STREAM.toString();
}
@@ -40,13 +40,11 @@ public abstract class VersionNumber implements Comparable<VersionNumber> {
throw new IllegalArgumentException("The version " + version + " is malformed, only dots and digits are allowed.");
String[] s = version.split("\\.");
int last = s.length - 1;
for (int i = s.length - 1; i >= 0; --i)
if (Integer.parseInt(s[i]) == 0)
last = i;
ArrayList<Integer> versions = new ArrayList<>(last + 1);
for (int i = 0; i <= last; ++i)
versions.add(Integer.parseInt(s[i]));
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));
}
@@ -70,10 +70,10 @@ public final class ZipEngine implements Closeable {
* modified pathName, null if you dont want this file zipped
*/
private void putDirectoryImpl(File source, String basePath, BiFunction<String, Boolean, String> pathNameCallback) throws IOException {
File[] files;
File[] files = null;
if (source.isDirectory())
files = source.listFiles();
else
else if (source.isFile())
files = new File[] { source };
if (files == null)
+11 -3
View File
@@ -28,8 +28,16 @@ Now you can launch game by constructing a `DefaultLauncher`.
DefaultLauncher launcher = new DefaultLauncher(
repository, // GameRepository
"test", // Your minecraft version name
OfflineAccountFactory.INSTANCE.fromUsername("player007").logIn(MultiCharacterSelector.DEFAULT), // account
// or YggdrasilAccountFactory.INSTANCE.fromUsername(username, password).logIn
new AccountBuilder.Builder()
.setUsername("playerId")
.setProxy(Proxy.NO_PROXY) // Optional
.create(OfflineAccountFactory.INSTANCE)
.logIn(), // account
// or new AccountBuilder.Builder()
// .setUsername("someone@xxx.com")
// .setPassword("123")
// .create(YggdrasilAccountFactory.INSTANCE)
// .logIn()
new LaunchOptions.Builder()
.setGameDir(repository.getBaseDirectory())
.setMaxMemory(...)
@@ -51,7 +59,7 @@ DefaultLauncher launcher = new DefaultLauncher(
System.out.println("Process exited then exit code " + exitCode);
}
},
false // true if launcher process exits, listening thread exit too.
false // true if launcher process exits, listening threads exit too.
);
```
Now you can simply call `launcher.launch()` to launch the game.