Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7bfb9a11d | ||
|
|
c9391181bd | ||
|
|
b08eb519bf | ||
|
|
47c4fc2f7e | ||
|
|
dfd177433a | ||
|
|
27381e9696 | ||
|
|
1c7827c53c | ||
|
|
9512bdb5d6 | ||
|
|
33d1f1d692 | ||
|
|
c6b49545ff | ||
|
|
a4a833bfdb | ||
|
|
2e1b6e4630 | ||
|
|
6911cfa387 |
No files matched your search
@@ -17,23 +17,19 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
@@ -42,7 +38,6 @@ import java.nio.file.Paths;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public final class Launcher extends Application {
|
||||
|
||||
@@ -58,24 +53,7 @@ public final class Launcher extends Application {
|
||||
primaryStage.setResizable(false);
|
||||
primaryStage.setScene(Controllers.getScene());
|
||||
|
||||
UpdateChecker.updateChannelProperty().addListener(observable -> {
|
||||
thread(() -> {
|
||||
try {
|
||||
UpdateChecker.checkUpdate();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to check for update", e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
UpdateChecker.updateChannelProperty().bind(Bindings.createStringBinding(() -> {
|
||||
switch (ConfigHolder.config().getUpdateChannel()) {
|
||||
case DEVELOPMENT:
|
||||
return UpdateChecker.CHANNEL_DEV;
|
||||
default:
|
||||
return UpdateChecker.CHANNEL_STABLE;
|
||||
}
|
||||
}, ConfigHolder.config().updateChannelProperty()));
|
||||
UpdateChecker.init();
|
||||
|
||||
primaryStage.show();
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -227,29 +227,50 @@ public final class LauncherHelper {
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8) {
|
||||
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
|
||||
// Minecraft 1.13 and later versions only support Java 8 or later.
|
||||
// Terminate launching operation.
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
// Game later than 1.7.2 accepts Java 8.
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8 && gameVersion.compareTo(VersionNumber.asVersion("1.7.2")) > 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
|
||||
// Minecraft 1.13 and later versions only support Java 8 or later.
|
||||
// Terminate launching operation.
|
||||
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// LaunchWrapper will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
// cpw has claimed that he will make MinecraftForge of 1.13 and later versions able to run on Java 9.
|
||||
if (!flag && 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.13")) < 0 && version.getMainClass().contains("launchwrapper")) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
|
||||
if (java8.isPresent()) {
|
||||
setting.setJavaVersion(java8.get());
|
||||
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
|
||||
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0.51");
|
||||
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJREs().stream()
|
||||
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageBox.WARNING_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
@@ -257,6 +278,7 @@ public final class LauncherHelper {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
@@ -264,6 +286,7 @@ public final class LauncherHelper {
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
|
||||
flag = true;
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.hildan.fxgson.creators.ObservableSetCreator;
|
||||
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.util.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.ObservableHelper;
|
||||
@@ -150,7 +151,7 @@ public final class Config implements Cloneable, Observable {
|
||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
|
||||
|
||||
@SerializedName("updateChannel")
|
||||
private ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<>(EnumUpdateChannel.STABLE);
|
||||
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
|
||||
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
@@ -438,15 +439,15 @@ public final class Config implements Cloneable, Observable {
|
||||
return authlibInjectorServers;
|
||||
}
|
||||
|
||||
public EnumUpdateChannel getUpdateChannel() {
|
||||
public UpdateChannel getUpdateChannel() {
|
||||
return updateChannel.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumUpdateChannel> updateChannelProperty() {
|
||||
public ObjectProperty<UpdateChannel> updateChannelProperty() {
|
||||
return updateChannel;
|
||||
}
|
||||
|
||||
public void setUpdateChannel(EnumUpdateChannel updateChannel) {
|
||||
public void setUpdateChannel(UpdateChannel updateChannel) {
|
||||
this.updateChannel.set(updateChannel);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import org.jackhuang.hmcl.ui.construct.FontComboBox;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
@@ -242,24 +243,24 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
|
||||
|
||||
lblUpdateNote.setWrappingWidth(470);
|
||||
|
||||
ObjectProperty<EnumUpdateChannel> updateChannel = new SimpleObjectProperty<EnumUpdateChannel>() {
|
||||
ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<UpdateChannel>() {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
EnumUpdateChannel updateChannel = Objects.requireNonNull(get());
|
||||
chkUpdateDev.setSelected(updateChannel == EnumUpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setSelected(updateChannel == EnumUpdateChannel.STABLE);
|
||||
UpdateChannel updateChannel = Objects.requireNonNull(get());
|
||||
chkUpdateDev.setSelected(updateChannel == UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setSelected(updateChannel == UpdateChannel.STABLE);
|
||||
}
|
||||
};
|
||||
|
||||
ToggleGroup updateChannelGroup = new ToggleGroup();
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateDev.setUserData(EnumUpdateChannel.DEVELOPMENT);
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateStable.setUserData(EnumUpdateChannel.STABLE);
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
updateChannelGroup.getToggles().forEach(
|
||||
toggle -> toggle.selectedProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
updateChannel.set((EnumUpdateChannel) toggle.getUserData());
|
||||
updateChannel.set((UpdateChannel) toggle.getUserData());
|
||||
}
|
||||
}));
|
||||
updateChannel.bindBidirectional(ConfigHolder.config().updateChannelProperty());
|
||||
|
||||
@@ -26,17 +26,17 @@ import javafx.beans.property.StringProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.FileUtils;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", null);
|
||||
|
||||
@@ -188,7 +188,11 @@ public final class VersionPage extends StackPane implements DecoratorPage {
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == EnumGameDirectory.VERSION_FOLDER;
|
||||
Controllers.confirmDialog(i18n(isIndependent ? "version.manage.remove.confirm.independent" : "version.manage.remove.confirm", version), i18n("message.confirm"), () -> {
|
||||
boolean isMovingToTrashSupported = FileUtils.isMovingToTrashSupported();
|
||||
String message = isIndependent ? i18n("version.manage.remove.confirm.independent", version) :
|
||||
isMovingToTrashSupported ? i18n("version.manage.remove.confirm.trash", version, version + "_removed") :
|
||||
i18n("version.manage.remove.confirm", version);
|
||||
Controllers.confirmDialog(message, i18n("message.confirm"), () -> {
|
||||
if (profile.getRepository().removeVersionFromDisk(version)) {
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
Controllers.navigate(null);
|
||||
|
||||
@@ -47,6 +47,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
private final DownloadProvider downloadProvider;
|
||||
private final String libraryId;
|
||||
private final String title;
|
||||
private final Runnable callback;
|
||||
|
||||
@FXML
|
||||
private JFXListView<VersionsPageItem> list;
|
||||
@@ -55,6 +56,8 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
@FXML
|
||||
private StackPane failedPane;
|
||||
@FXML
|
||||
private StackPane emptyPane;
|
||||
@FXML
|
||||
private JFXCheckBox chkRelease;
|
||||
@FXML
|
||||
private JFXCheckBox chkSnapshot;
|
||||
@@ -74,6 +77,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
this.gameVersion = gameVersion;
|
||||
this.downloadProvider = downloadProvider;
|
||||
this.libraryId = libraryId;
|
||||
this.callback = callback;
|
||||
this.versionList = downloadProvider.getVersionListById(libraryId);
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/download/versions.fxml");
|
||||
@@ -100,8 +104,6 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
private List<VersionsPageItem> loadVersions() {
|
||||
return versionList.getVersions(gameVersion).stream()
|
||||
.filter(it -> {
|
||||
if (it.getVersionType() == null)
|
||||
return true;
|
||||
switch (it.getVersionType()) {
|
||||
case RELEASE:
|
||||
return chkRelease.isSelected();
|
||||
@@ -125,8 +127,12 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
List<VersionsPageItem> items = loadVersions();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
list.getItems().setAll(items);
|
||||
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
if (items.isEmpty()) {
|
||||
transitionHandler.setContent(emptyPane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
} else {
|
||||
list.getItems().setAll(items);
|
||||
transitionHandler.setContent(centrePane, ContainerAnimations.FADE.getAnimationProducer());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
@@ -152,4 +158,7 @@ public final class VersionsPage extends StackPane implements WizardPage, Refresh
|
||||
private void onRefresh() {
|
||||
refresh();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onBack() { callback.run(); }
|
||||
}
|
||||
+11
-5
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <[email protected]>
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,9 +15,15 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
public enum EnumUpdateChannel {
|
||||
STABLE,
|
||||
DEVELOPMENT
|
||||
public enum UpdateChannel {
|
||||
STABLE("stable"),
|
||||
DEVELOPMENT("dev");
|
||||
|
||||
public final String channelName;
|
||||
|
||||
UpdateChannel(String channelName) {
|
||||
this.channelName = channelName;
|
||||
}
|
||||
}
|
||||
@@ -17,16 +17,20 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.VersionNumber.asVersion;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.jfoenix.concurrency.JFXUtilities;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.ImmediateStringProperty;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import javafx.application.Platform;
|
||||
@@ -37,11 +41,6 @@ import javafx.beans.value.ObservableBooleanValue;
|
||||
public final class UpdateChecker {
|
||||
private UpdateChecker() {}
|
||||
|
||||
public static final String CHANNEL_STABLE = "stable";
|
||||
public static final String CHANNEL_DEV = "dev";
|
||||
|
||||
private static StringProperty updateChannel = new ImmediateStringProperty(null, "updateChannel", CHANNEL_STABLE);
|
||||
|
||||
private static ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
|
||||
private static BooleanBinding outdated = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
@@ -55,16 +54,9 @@ public final class UpdateChecker {
|
||||
latestVersion);
|
||||
private static ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
public static String getUpdateChannel() {
|
||||
return updateChannel.get();
|
||||
}
|
||||
|
||||
public static void setUpdateChannel(String updateChannel) {
|
||||
UpdateChecker.updateChannel.set(updateChannel);
|
||||
}
|
||||
|
||||
public static StringProperty updateChannelProperty() {
|
||||
return updateChannel;
|
||||
public static void init() {
|
||||
ConfigHolder.config().updateChannelProperty().addListener(onInvalidating(UpdateChecker::requestCheckUpdate));
|
||||
requestCheckUpdate();
|
||||
}
|
||||
|
||||
public static RemoteVersion getLatestVersion() {
|
||||
@@ -91,35 +83,53 @@ public final class UpdateChecker {
|
||||
return checkingUpdate.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static void checkUpdate() throws IOException {
|
||||
private static RemoteVersion checkUpdate(UpdateChannel channel) throws IOException {
|
||||
if (!IntegrityChecker.isSelfVerified()) {
|
||||
return;
|
||||
throw new IOException("Self verification failed");
|
||||
}
|
||||
|
||||
JFXUtilities.runInFXAndWait(() -> {
|
||||
checkingUpdate.set(true);
|
||||
});
|
||||
String url = NetworkUtils.withQuery(Metadata.UPDATE_URL, mapOf(
|
||||
pair("version", Metadata.VERSION),
|
||||
pair("channel", channel.channelName)));
|
||||
|
||||
try {
|
||||
|
||||
String channel = getUpdateChannel();
|
||||
String url = NetworkUtils.withQuery(Metadata.UPDATE_URL, mapOf(
|
||||
pair("version", Metadata.VERSION),
|
||||
pair("channel", channel)));
|
||||
|
||||
RemoteVersion fetched = RemoteVersion.fetch(url);
|
||||
Platform.runLater(() -> {
|
||||
if (channel.equals(getUpdateChannel())) {
|
||||
latestVersion.set(fetched);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
Platform.runLater(() -> checkingUpdate.set(false));
|
||||
}
|
||||
return RemoteVersion.fetch(url);
|
||||
}
|
||||
|
||||
private static boolean isDevelopmentVersion(String version) {
|
||||
return version.contains("@") || // eg. @develop@
|
||||
version.contains("SNAPSHOT"); // eg. 3.1.SNAPSHOT
|
||||
}
|
||||
|
||||
public static void requestCheckUpdate() {
|
||||
Platform.runLater(() -> {
|
||||
if (isCheckingUpdate())
|
||||
return;
|
||||
checkingUpdate.set(true);
|
||||
UpdateChannel channel = config().getUpdateChannel();
|
||||
|
||||
thread(() -> {
|
||||
RemoteVersion result = null;
|
||||
try {
|
||||
result = checkUpdate(channel);
|
||||
LOG.info("Latest version (" + channel + ") is " + result);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to check for update", e);
|
||||
}
|
||||
|
||||
RemoteVersion finalResult = result;
|
||||
Platform.runLater(() -> {
|
||||
checkingUpdate.set(false);
|
||||
if (finalResult != null) {
|
||||
if (channel.equals(config().getUpdateChannel())) {
|
||||
latestVersion.set(finalResult);
|
||||
} else {
|
||||
// the channel has been changed during the period
|
||||
// check update again
|
||||
requestCheckUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -20,4 +20,7 @@
|
||||
<StackPane fx:id="failedPane">
|
||||
<Label onMouseClicked="#onRefresh" style="-fx-text-fill: #0079FF; -fx-font-size: 20;" text="%download.failed.refresh" />
|
||||
</StackPane>
|
||||
<StackPane fx:id="emptyPane">
|
||||
<Label onMouseClicked="#onBack" style="-fx-text-fill: #0079FF; -fx-font-size: 20;" text="%download.failed.empty" />
|
||||
</StackPane>
|
||||
</fx:root>
|
||||
@@ -83,6 +83,7 @@ crash.user_fault=Your OS or Java environment may not be properly installed resul
|
||||
download=Download
|
||||
download.BMCL=BMCLAPI (bangbang93, https://bmclapi2.bangbang93.com/)
|
||||
download.failed=Failed to download
|
||||
download.failed.empty=No candidates. Click here to return.
|
||||
download.failed.refresh=Unable to load version list. Click here to retry.
|
||||
download.mojang=Mojang
|
||||
|
||||
@@ -134,6 +135,7 @@ lang.default=Use system language
|
||||
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. You are supposed to install the latest version of Java 8.
|
||||
launch.advice.java9=You cannot launch Minecraft until game version is higher than 1.13 with Java 9 or later Java versions.
|
||||
launch.advice.newer_java=Java 8 is suggested, which can make game run more fluently. And many mods and Minecraft 1.12 and newer versions requires Java 8.
|
||||
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game probably crash. The launcher will try to launch it.
|
||||
@@ -334,7 +336,8 @@ version.launch_script.save=Save the launch script
|
||||
version.launch_script.success=Finished script creation, %s.
|
||||
version.manage.redownload_assets_index=Redownload Assets Index
|
||||
version.manage.remove=Delete this game
|
||||
version.manage.remove.confirm=Sure to remove game %s?
|
||||
version.manage.remove.confirm=Sure to remove game %s? You cannot restore this game again!
|
||||
version.manage.remove.confirm.trash=Sure to remove game %s? You can restore the game with the name of %s in your system trash.
|
||||
version.manage.remove.confirm.independent=Since this game is in independent mode, deleting this game will result in all saved worlds belonging to this game removed simultaneously. Sure to remove game %s?
|
||||
version.manage.remove_libraries=Delete library files
|
||||
version.manage.rename=Rename this game
|
||||
|
||||
@@ -83,6 +83,7 @@ crash.user_fault=您的系統或 Java 環境可能安裝不當導致本軟件崩
|
||||
download=下載
|
||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.failed=下載失敗
|
||||
download.failed.empty=沒有可供安裝的版本,點擊此處返回。
|
||||
download.failed.refresh=加載版本列表失敗,點擊此處重試。
|
||||
download.mojang=官方
|
||||
|
||||
@@ -134,6 +135,7 @@ lang.default=跟隨系統語言
|
||||
|
||||
launch.advice.different_platform=您的系統是 64 位的但是 Java 是 32 位的,推薦您安裝 64 位 Java。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,請使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=舊於 1.8.0_51 的 Java 可能會導致 Minecraft 1.13 崩潰。建議您到 https://java.com 安裝最新版的Java 8。
|
||||
launch.advice.java9=不高於 1.13 的安裝了 Mod 的 Minecraft 不支持 Java 9 或更高版本,請使用 Java 8。
|
||||
launch.advice.newer_java=檢測到您未使用 Java 8 及更新版本,Java 8 能使遊戲更流暢而且 Minecraft 1.12 及更新版本和很多 Mod 強制需要需要 Java 8。
|
||||
launch.advice.not_enough_space=您設置的內存大小過大,由於超過了系統內存大小 %dMB,所以可能影響遊戲體驗或無法啓動遊戲,啓動器仍會嘗試啓動。
|
||||
@@ -334,7 +336,8 @@ version.launch_script.save=保存啓動腳本
|
||||
version.launch_script.success=啓動腳本已生成完畢:%s
|
||||
version.manage.redownload_assets_index=重新下載資源配置(assets_index.json)
|
||||
version.manage.remove=刪除該版本
|
||||
version.manage.remove.confirm=真的要刪除版本 %s 嗎?
|
||||
version.manage.remove.confirm=真的要刪除版本 %s 嗎?你將無法找回被刪除的文件!
|
||||
version.manage.remove.confirm.trash=真的要刪除版本 %s 嗎?你可以在系統的回收站中恢復文件夾 %s 來找回該版本。
|
||||
version.manage.remove.confirm.independent=由於該遊戲使用了版本隔離,所以刪除該版本將導致該遊戲的存檔等數據一同被刪除,真的要刪除版本 %s 嗎?
|
||||
version.manage.remove_libraries=刪除所有庫文件
|
||||
version.manage.rename=重命名該版本
|
||||
|
||||
@@ -83,6 +83,7 @@ crash.user_fault=您的系统或 Java 环境可能安装不当导致本软件崩
|
||||
download=下载
|
||||
download.BMCL=BMCLAPI(bangbang93,https://bmclapi2.bangbang93.com/)
|
||||
download.failed=下载失败
|
||||
download.failed.empty=没有可供安装的版本,点击此处返回。
|
||||
download.failed.refresh=加载版本列表失败,点击此处重试。
|
||||
download.mojang=官方
|
||||
|
||||
@@ -134,6 +135,7 @@ lang.default=跟随系统语言
|
||||
|
||||
launch.advice.different_platform=您的系统是 64 位的但是 Java 是 32 位的,推荐您安装 64 位 Java。
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=旧于 1.8.0_51 的 Java 可能会导致 Minecraft 1.13 崩溃。建议您到 https://java.com 安装最新版的 Java 8。
|
||||
launch.advice.java9=不高于 1.13 的安装了 Mod 的 Minecraft 不支持 Java 9 或更高版本,请使用 Java 8。
|
||||
launch.advice.newer_java=检测到您未使用 Java 8 及更新版本,Java 8 能使游戏更流畅而且 Minecraft 1.12 及更新版本和很多 Mod 强制需要需要 Java 8。
|
||||
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
|
||||
@@ -334,7 +336,8 @@ version.launch_script.save=保存启动脚本
|
||||
version.launch_script.success=启动脚本已生成完毕:%s
|
||||
version.manage.redownload_assets_index=重新下载资源配置(assets_index.json)
|
||||
version.manage.remove=删除该版本
|
||||
version.manage.remove.confirm=真的要删除版本 %s 吗?
|
||||
version.manage.remove.confirm=真的要删除版本 %s 吗?你将无法找回被删除的文件!
|
||||
version.manage.remove.confirm.trash=真的要删除版本 %s 吗?你可以在系统的回收站中恢复文件夹 %s 来找回该版本。
|
||||
version.manage.remove.confirm.independent=由于该游戏使用了版本隔离,所以删除该版本将导致该游戏的存档等数据一同被删除,真的要删除版本 %s 吗?
|
||||
version.manage.remove_libraries=删除所有库文件
|
||||
version.manage.rename=重命名该版本
|
||||
|
||||
@@ -65,7 +65,6 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
public Task checkGameCompletionAsync(Version version) {
|
||||
return new ParallelTask(
|
||||
new GameAssetDownloadTask(this, version),
|
||||
new GameLoggingDownloadTask(this, version),
|
||||
new GameLibrariesTask(this, version)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,6 @@ public class DefaultGameBuilder extends GameBuilder {
|
||||
variables.set("version", version);
|
||||
Task result = new ParallelTask(
|
||||
new GameAssetDownloadTask(dependencyManager, version),
|
||||
new GameLoggingDownloadTask(dependencyManager, version),
|
||||
downloadGameAsync(gameVersion, version),
|
||||
new GameLibrariesTask(dependencyManager, version) // Game libraries will be downloaded for multiple times partly, this time is for vanilla libraries.
|
||||
).with(new VersionJsonSaveTask(dependencyManager.getGameRepository(), version)); // using [with] because download failure here are tolerant.
|
||||
|
||||
@@ -94,7 +94,9 @@ public class MaintainTask extends TaskResult<Version> {
|
||||
args.add("optifine.OptiFineForgeTweaker");
|
||||
}
|
||||
|
||||
return version.setMinecraftArguments(new CommandBuilder().addAll(args).toString());
|
||||
// Since $ will be escaped in linux, and our maintain of minecraftArgument will not cause escaping,
|
||||
// so we regenerate the minecraftArgument without escaping.
|
||||
return version.setMinecraftArguments(new CommandBuilder().addAllWithoutParsing(args).toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,7 +42,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
* @param url the installer or universal jar URL.
|
||||
*/
|
||||
public RemoteVersion(String gameVersion, String selfVersion, String url) {
|
||||
this(gameVersion, selfVersion, url, null);
|
||||
this(gameVersion, selfVersion, url, Type.UNCATEGORIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
this.gameVersion = Objects.requireNonNull(gameVersion);
|
||||
this.selfVersion = Objects.requireNonNull(selfVersion);
|
||||
this.url = Objects.requireNonNull(url);
|
||||
this.type = type;
|
||||
this.type = Objects.requireNonNull(type);
|
||||
}
|
||||
|
||||
public String getGameVersion() {
|
||||
@@ -100,6 +100,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
UNCATEGORIZED,
|
||||
RELEASE,
|
||||
SNAPSHOT,
|
||||
OLD
|
||||
|
||||
@@ -73,8 +73,7 @@ public abstract class VersionList<T extends RemoteVersion> {
|
||||
private Collection<T> getVersionsImpl(String gameVersion) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
Collection<T> ans = versions.get(gameVersion);
|
||||
return ans.isEmpty() ? versions.values() : ans;
|
||||
return versions.get(gameVersion);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.DependencyManager;
|
||||
import org.jackhuang.hmcl.game.DownloadType;
|
||||
import org.jackhuang.hmcl.game.LoggingInfo;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This task is to download log4j configuration file provided in minecraft.json.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameLoggingDownloadTask extends Task {
|
||||
|
||||
private final DependencyManager dependencyManager;
|
||||
private final Version version;
|
||||
private final List<Task> dependencies = new LinkedList<>();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param dependencyManager the dependency manager that can provides {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
* @param version the <b>resolved</b> version
|
||||
*/
|
||||
public GameLoggingDownloadTask(DependencyManager dependencyManager, Version version) {
|
||||
this.dependencyManager = dependencyManager;
|
||||
this.version = version;
|
||||
setSignificance(TaskSignificance.MODERATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Task> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if (version.getLogging() == null || !version.getLogging().containsKey(DownloadType.CLIENT))
|
||||
return;
|
||||
|
||||
LoggingInfo logging = version.getLogging().get(DownloadType.CLIENT);
|
||||
File file = dependencyManager.getGameRepository().getLoggingObject(version.getId(), version.getAssetIndex().getId(), logging);
|
||||
if (!file.exists())
|
||||
dependencies.add(new FileDownloadTask(NetworkUtils.toURL(logging.getFile().getUrl()), file));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,7 +34,7 @@ public final class GameRemoteVersion extends RemoteVersion {
|
||||
private final Date time;
|
||||
|
||||
public GameRemoteVersion(String gameVersion, String selfVersion, String url, ReleaseType type, Date time) {
|
||||
super(gameVersion, selfVersion, url);
|
||||
super(gameVersion, selfVersion, url, getReleaseType(type));
|
||||
this.type = type;
|
||||
this.time = time;
|
||||
}
|
||||
@@ -54,4 +54,18 @@ public final class GameRemoteVersion extends RemoteVersion {
|
||||
|
||||
return ((GameRemoteVersion) o).getTime().compareTo(getTime());
|
||||
}
|
||||
|
||||
private static Type getReleaseType(ReleaseType type) {
|
||||
if (type == null) return Type.UNCATEGORIZED;
|
||||
switch (type) {
|
||||
case RELEASE:
|
||||
return Type.RELEASE;
|
||||
case SNAPSHOT:
|
||||
return Type.SNAPSHOT;
|
||||
case UNKNOWN:
|
||||
return Type.UNCATEGORIZED;
|
||||
default:
|
||||
return Type.OLD;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +142,7 @@ public final class LibraryDownloadTask extends Task {
|
||||
JarInputStream jar = new JarInputStream(new ByteArrayInputStream(data));
|
||||
JarEntry entry = jar.getNextJarEntry();
|
||||
while (entry != null) {
|
||||
byte[] eData = IOUtils.readFullyAsByteArray(jar);
|
||||
byte[] eData = IOUtils.readFullyWithoutClosing(jar);
|
||||
if (entry.getName().equals("checksums.sha1")) {
|
||||
hashes = new String(eData, Charset.forName("UTF-8")).split("\n");
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ public final class Arguments {
|
||||
}
|
||||
|
||||
public static List<String> parseStringArguments(List<String> arguments, Map<String, String> keys) {
|
||||
return arguments.stream().map(str -> keys.getOrDefault(str, str)).collect(Collectors.toList());
|
||||
return arguments.stream().flatMap(str -> new StringArgument(str).toString(keys, Collections.emptyMap()).stream()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static List<String> parseArguments(List<Argument> arguments, Map<String, String> keys) {
|
||||
|
||||
@@ -160,14 +160,18 @@ public class DefaultGameRepository implements GameRepository {
|
||||
if (!file.renameTo(removedFile))
|
||||
return false;
|
||||
|
||||
versions.remove(id);
|
||||
|
||||
if (FileUtils.isMovingToTrashSupported()) {
|
||||
return FileUtils.moveToTrash(removedFile);
|
||||
}
|
||||
|
||||
// remove json files first to ensure HMCL will not recognize this folder as a valid version.
|
||||
List<File> jsons = FileUtils.listFilesByExtension(removedFile, "json");
|
||||
jsons.forEach(f -> {
|
||||
if (!f.delete())
|
||||
Logging.LOG.warning("Unable to delete file " + f);
|
||||
});
|
||||
|
||||
versions.remove(id);
|
||||
// remove the version from version list regardless of whether the directory was removed successfully or not.
|
||||
try {
|
||||
FileUtils.deleteDirectory(removedFile);
|
||||
|
||||
@@ -223,6 +223,7 @@ public class Library implements Comparable<Library> {
|
||||
obj.addProperty("name", src.getName());
|
||||
obj.addProperty("url", src.url);
|
||||
obj.add("downloads", context.serialize(src.downloads));
|
||||
obj.add("checksums", context.serialize(src.getChecksums()));
|
||||
obj.add("extract", context.serialize(src.extract));
|
||||
obj.add("natives", context.serialize(src.natives, new TypeToken<Map<OperatingSystem, String>>() {
|
||||
}.getType()));
|
||||
|
||||
@@ -65,7 +65,7 @@ public class RuledArgument implements Argument {
|
||||
|
||||
@Override
|
||||
public List<String> toString(Map<String, String> keys, Map<String, Boolean> features) {
|
||||
if (CompatibilityRule.appliesToCurrentEnvironment(rules) && value != null)
|
||||
if (CompatibilityRule.appliesToCurrentEnvironment(rules, features) && value != null)
|
||||
return value.stream()
|
||||
.map(StringArgument::new)
|
||||
.map(str -> str.toString(keys, features).get(0))
|
||||
|
||||
@@ -47,7 +47,7 @@ public class DefaultLauncher extends Launcher {
|
||||
super(repository, versionId, authInfo, options, listener, daemon);
|
||||
}
|
||||
|
||||
private CommandBuilder generateCommandLine(File nativeFolder, boolean enableLoggingInfo) throws IOException {
|
||||
private CommandBuilder generateCommandLine(File nativeFolder) throws IOException {
|
||||
CommandBuilder res = new CommandBuilder();
|
||||
|
||||
// Executable
|
||||
@@ -70,16 +70,6 @@ public class DefaultLauncher extends Launcher {
|
||||
res.add("-Xdock:icon=" + repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), "icons/minecraft.icns").getAbsolutePath());
|
||||
}
|
||||
|
||||
Map<DownloadType, LoggingInfo> logging = version.getLogging();
|
||||
if (logging != null && enableLoggingInfo) {
|
||||
LoggingInfo loggingInfo = logging.get(DownloadType.CLIENT);
|
||||
if (loggingInfo != null) {
|
||||
File loggingFile = repository.getLoggingObject(version.getId(), version.getAssetIndex().getId(), loggingInfo);
|
||||
if (loggingFile.exists())
|
||||
res.add(loggingInfo.getArgument().replace("${path}", loggingFile.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
|
||||
res.add("-Duser.home=" + options.getGameDir().getParent());
|
||||
|
||||
@@ -153,14 +143,6 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
res.addAll(Arguments.parseStringArguments(version.getMinecraftArguments().map(StringUtils::tokenize).orElseGet(LinkedList::new), configuration));
|
||||
|
||||
// Optional Minecraft arguments
|
||||
if (options.getHeight() != null && options.getHeight() != 0 && options.getWidth() != null && options.getWidth() != 0) {
|
||||
res.add("--height");
|
||||
res.add(options.getHeight().toString());
|
||||
res.add("--width");
|
||||
res.add(options.getWidth().toString());
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(options.getServerIp())) {
|
||||
String[] args = options.getServerIp().split(":");
|
||||
res.add("--server");
|
||||
@@ -249,7 +231,9 @@ public class DefaultLauncher extends Launcher {
|
||||
pair("${game_directory}", repository.getRunDirectory(version.getId()).getAbsolutePath()),
|
||||
pair("${user_type}", "mojang"),
|
||||
pair("${assets_index_name}", version.getAssetIndex().getId()),
|
||||
pair("${user_properties}", authInfo.getUserProperties())
|
||||
pair("${user_properties}", authInfo.getUserProperties()),
|
||||
pair("${resolution_width}", options.getWidth().toString()),
|
||||
pair("${resolution_height}", options.getHeight().toString())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -258,7 +242,7 @@ public class DefaultLauncher extends Launcher {
|
||||
File nativeFolder = Files.createTempDirectory("minecraft").toFile();
|
||||
|
||||
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
|
||||
List<String> rawCommandLine = generateCommandLine(nativeFolder, isEnablingLoggingInfo()).asList();
|
||||
List<String> rawCommandLine = generateCommandLine(nativeFolder).asList();
|
||||
|
||||
decompressNatives(nativeFolder);
|
||||
|
||||
@@ -310,50 +294,17 @@ public class DefaultLauncher extends Launcher {
|
||||
writer.write(options.getPreLaunchCommand());
|
||||
writer.newLine();
|
||||
}
|
||||
writer.write(generateCommandLine(nativeFolder, false).toString());
|
||||
writer.write(generateCommandLine(nativeFolder).toString());
|
||||
}
|
||||
if (!scriptFile.setExecutable(true))
|
||||
throw new PermissionException();
|
||||
}
|
||||
|
||||
protected boolean isEnablingLoggingInfo() {
|
||||
return version.getLogging() != null && version.getLogging().containsKey(DownloadType.CLIENT)
|
||||
&& !"net.minecraft.launchwrapper.Launch".equals(version.getMainClass());
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener) {
|
||||
startMonitors(managedProcess, processListener, true);
|
||||
}
|
||||
|
||||
private void startMonitors(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
|
||||
if (isEnablingLoggingInfo())
|
||||
startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon);
|
||||
else
|
||||
startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon);
|
||||
}
|
||||
|
||||
private void startMonitorsWithLoggingInfo(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
|
||||
processListener.setProcess(managedProcess);
|
||||
Log4jHandler logHandler = new Log4jHandler((line, level) -> {
|
||||
processListener.onLog(line, level);
|
||||
managedProcess.addLine(line);
|
||||
});
|
||||
logHandler.start();
|
||||
managedProcess.addRelatedThread(logHandler);
|
||||
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), logHandler::newLine), "stdout-pump", isDaemon);
|
||||
managedProcess.addRelatedThread(stdout);
|
||||
Thread stderr = Lang.thread(new StreamPump(managedProcess.getProcess().getErrorStream(), it -> {
|
||||
processListener.onLog(it + OperatingSystem.LINE_SEPARATOR, Log4jLevel.ERROR);
|
||||
managedProcess.addLine(it);
|
||||
}), "stderr-pump", isDaemon);
|
||||
managedProcess.addRelatedThread(stderr);
|
||||
managedProcess.addRelatedThread(Lang.thread(new ExitWaiter(managedProcess, Arrays.asList(stdout, stderr), (exitCode, exitType) -> {
|
||||
logHandler.onStopped();
|
||||
processListener.onExit(exitCode, exitType);
|
||||
}), "exit-waiter", isDaemon));
|
||||
}
|
||||
|
||||
private void startMonitorsWithoutLoggingInfo(ManagedProcess managedProcess, ProcessListener processListener, boolean isDaemon) {
|
||||
processListener.setProcess(managedProcess);
|
||||
Thread stdout = Lang.thread(new StreamPump(managedProcess.getProcess().getInputStream(), it -> {
|
||||
processListener.onLog(it + OperatingSystem.LINE_SEPARATOR, Optional.ofNullable(Log4jLevel.guessLevel(it)).orElse(Log4jLevel.INFO));
|
||||
|
||||
@@ -1,180 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.launch;
|
||||
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
import org.xml.sax.helpers.XMLReaderFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* This class is to parse log4j classic XML layout logging,
|
||||
* since only vanilla Minecraft will enable this layout.
|
||||
*
|
||||
* Also supports plain logs.
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
final class Log4jHandler extends Thread {
|
||||
|
||||
private final XMLReader reader;
|
||||
private final BiConsumer<String, Log4jLevel> callback;
|
||||
private final PipedOutputStream outputStream = new PipedOutputStream();
|
||||
private final PipedInputStream inputStream = Lang.invoke(() -> new PipedInputStream(outputStream));
|
||||
private final AtomicBoolean interrupted = new AtomicBoolean(false);
|
||||
private final List<String> logs = new LinkedList<>();
|
||||
private boolean broken = false;
|
||||
|
||||
public Log4jHandler(BiConsumer<String, Log4jLevel> callback) {
|
||||
this.callback = callback;
|
||||
newLine("<output>");
|
||||
|
||||
reader = Lang.invoke((ExceptionalSupplier<XMLReader, SAXException>) XMLReaderFactory::createXMLReader);
|
||||
reader.setContentHandler(new Log4jHandlerImpl());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
setName("log4j-handler");
|
||||
|
||||
try {
|
||||
InputSource inputSource = new InputSource(inputStream);
|
||||
reader.parse(inputSource);
|
||||
} catch (InterruptedIOException e) {
|
||||
// Game has been interrupted.
|
||||
interrupted.set(true);
|
||||
} catch (SAXException | IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "An error occurred when reading console lines", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onStopped() {
|
||||
if (interrupted.get())
|
||||
return;
|
||||
|
||||
Lang.invoke(() -> Schedulers.newThread().schedule(() -> {
|
||||
if (!interrupted.get()) {
|
||||
newLine("</output>").get();
|
||||
outputStream.close();
|
||||
join();
|
||||
}
|
||||
}).get());
|
||||
}
|
||||
|
||||
public Future<?> newLine(String log) {
|
||||
return Schedulers.computation().schedule(() -> {
|
||||
try {
|
||||
// Prevent from namespace parsing
|
||||
String line = log.replace("log4j:", "log4j_");
|
||||
logs.add(line);
|
||||
if (broken)
|
||||
System.out.println(line);
|
||||
|
||||
byte[] bytes = (line + OperatingSystem.LINE_SEPARATOR).getBytes(Constants.SYSTEM_CHARSET);
|
||||
outputStream.write(bytes);
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
// Ignoring IOException, including read end dead.
|
||||
if (!broken) {
|
||||
Logging.LOG.log(Level.WARNING, "An error occurred when writing console lines", e);
|
||||
logs.forEach(System.out::println);
|
||||
broken = true;
|
||||
} else {
|
||||
// Output plain XML to user
|
||||
callback.accept(log, Log4jLevel.INFO);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class Log4jHandlerImpl extends DefaultHandler {
|
||||
|
||||
private final SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
|
||||
|
||||
private String date = "", thread = "", logger = "";
|
||||
private StringBuilder message;
|
||||
private Log4jLevel level;
|
||||
private boolean readingMessage = false;
|
||||
|
||||
@Override
|
||||
public void startElement(String uri, String localName, String qName, Attributes attributes) {
|
||||
switch (localName) {
|
||||
case "log4j_Event":
|
||||
message = new StringBuilder();
|
||||
Date d = new Date(Long.parseLong(attributes.getValue("timestamp")));
|
||||
date = format.format(d);
|
||||
try {
|
||||
level = Log4jLevel.valueOf(attributes.getValue("level"));
|
||||
} catch (IllegalArgumentException e) {
|
||||
level = Log4jLevel.INFO;
|
||||
}
|
||||
thread = attributes.getValue("thread");
|
||||
logger = attributes.getValue("logger");
|
||||
if ("STDERR".equals(logger))
|
||||
level = Log4jLevel.ERROR;
|
||||
break;
|
||||
case "log4j_Message":
|
||||
readingMessage = true;
|
||||
break;
|
||||
case "log4j_Throwable":
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endElement(String uri, String localName, String qName) {
|
||||
switch (localName) {
|
||||
case "log4j_Event":
|
||||
callback.accept("[" + date + "] [" + thread + "/" + level.name() + "] [" + logger + "] " + message.toString(), level);
|
||||
break;
|
||||
case "log4j_Message":
|
||||
readingMessage = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) {
|
||||
String line = new String(ch, start, length);
|
||||
if (line.trim().isEmpty())
|
||||
return;
|
||||
if (readingMessage)
|
||||
message.append(line).append(OperatingSystem.LINE_SEPARATOR);
|
||||
else
|
||||
callback.accept(line, Optional.ofNullable(Log4jLevel.guessLevel(line)).orElse(Log4jLevel.INFO));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ public final class ModInfo implements Comparable<ModInfo> {
|
||||
}
|
||||
|
||||
public ModInfo(File file, String name, String description) {
|
||||
this(file, name, description, "unknown", "unknown", "unknown", "");
|
||||
this(file, name, description, "", "", "", "");
|
||||
}
|
||||
|
||||
public ModInfo(File file, String name, String description, String authors, String version, String gameVersion, String url) {
|
||||
|
||||
@@ -20,8 +20,10 @@ package org.jackhuang.hmcl.util;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -95,6 +97,30 @@ public final class FileUtils {
|
||||
return Lang.test(() -> deleteDirectory(directory));
|
||||
}
|
||||
|
||||
public static boolean moveToTrash(File file) {
|
||||
try {
|
||||
java.awt.Desktop desktop = java.awt.Desktop.getDesktop();
|
||||
Method moveToTrash = desktop.getClass().getMethod("moveToTrash", File.class);
|
||||
moveToTrash.invoke(desktop, file);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if {@code java.awt.Desktop.moveToTrash} exists.
|
||||
* @return true if the method exists.
|
||||
*/
|
||||
public static boolean isMovingToTrashSupported() {
|
||||
try {
|
||||
java.awt.Desktop.class.getMethod("moveToTrash", File.class);
|
||||
return true;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void cleanDirectory(File directory)
|
||||
throws IOException {
|
||||
if (!directory.exists()) {
|
||||
|
||||
@@ -31,6 +31,12 @@ public final class IOUtils {
|
||||
|
||||
public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;
|
||||
|
||||
public static byte[] readFullyWithoutClosing(InputStream stream) throws IOException {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
copyTo(stream, result);
|
||||
return result.toByteArray();
|
||||
}
|
||||
|
||||
public static ByteArrayOutputStream readFully(InputStream stream) throws IOException {
|
||||
try (InputStream is = stream) {
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
|
||||
@@ -21,13 +21,11 @@ import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleBooleanProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateBooleanProperty extends SimpleBooleanProperty {
|
||||
|
||||
@Override
|
||||
@@ -48,31 +46,9 @@ public class ImmediateBooleanProperty extends SimpleBooleanProperty {
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
private Consumer<Boolean> consumer = null;
|
||||
private ChangeListener<Boolean> listener = null;
|
||||
|
||||
public void setChangedListener(Consumer<Boolean> consumer) {
|
||||
this.consumer = Objects.requireNonNull(consumer);
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
public void setChangedListener(ChangeListener<Boolean> listener) {
|
||||
this.consumer = null;
|
||||
this.listener = Objects.requireNonNull(listener);
|
||||
}
|
||||
|
||||
public void setChangedListenerAndOperate(Consumer<Boolean> listener) {
|
||||
setChangedListener(listener);
|
||||
listener.accept(get());
|
||||
}
|
||||
|
||||
public ImmediateBooleanProperty(Object bean, String name, boolean initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<Boolean> changeListener = (a, b, newValue) -> {
|
||||
if (consumer != null)
|
||||
consumer.accept(newValue);
|
||||
if (listener != null)
|
||||
listener.changed(a, b, newValue);
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2018 huangyuhui <[email protected]>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class ImmediateDoubleProperty extends SimpleDoubleProperty {
|
||||
|
||||
@Override
|
||||
public void set(double newValue) {
|
||||
super.get();
|
||||
super.set(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ObservableValue<? extends Number> newObservable) {
|
||||
super.get();
|
||||
super.bind(newObservable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unbind() {
|
||||
super.get();
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
private Consumer<Double> consumer = null;
|
||||
private ChangeListener<Number> listener = null;
|
||||
|
||||
public void setChangedListener(Consumer<Double> consumer) {
|
||||
this.consumer = Objects.requireNonNull(consumer);
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
public void setChangedListener(ChangeListener<Number> listener) {
|
||||
this.consumer = null;
|
||||
this.listener = Objects.requireNonNull(listener);
|
||||
}
|
||||
|
||||
public void setChangedListenerAndOperate(Consumer<Double> listener) {
|
||||
setChangedListener(listener);
|
||||
listener.accept(get());
|
||||
}
|
||||
|
||||
public ImmediateDoubleProperty(Object bean, String name, double initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<Number> changeListener = (a, b, newValue) -> {
|
||||
if (consumer != null)
|
||||
consumer.accept(newValue.doubleValue());
|
||||
if (listener != null)
|
||||
listener.changed(a, b, newValue);
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,11 @@ import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleIntegerProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateIntegerProperty extends SimpleIntegerProperty {
|
||||
|
||||
@Override
|
||||
@@ -48,31 +46,9 @@ public class ImmediateIntegerProperty extends SimpleIntegerProperty {
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
private Consumer<Integer> consumer = null;
|
||||
private ChangeListener<Number> listener = null;
|
||||
|
||||
public void setChangedListener(Consumer<Integer> consumer) {
|
||||
this.consumer = Objects.requireNonNull(consumer);
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
public void setChangedListener(ChangeListener<Number> listener) {
|
||||
this.consumer = null;
|
||||
this.listener = Objects.requireNonNull(listener);
|
||||
}
|
||||
|
||||
public void setChangedListenerAndOperate(Consumer<Integer> listener) {
|
||||
setChangedListener(listener);
|
||||
listener.accept(get());
|
||||
}
|
||||
|
||||
public ImmediateIntegerProperty(Object bean, String name, int initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<Number> changeListener = (a, b, newValue) -> {
|
||||
if (consumer != null)
|
||||
consumer.accept(newValue.intValue());
|
||||
if (listener != null)
|
||||
listener.changed(a, b, newValue);
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,11 @@ import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleObjectProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateObjectProperty<T> extends SimpleObjectProperty<T> {
|
||||
|
||||
@Override
|
||||
@@ -48,31 +46,9 @@ public class ImmediateObjectProperty<T> extends SimpleObjectProperty<T> {
|
||||
super.unbind();
|
||||
}
|
||||
|
||||
private Consumer<T> consumer = null;
|
||||
private ChangeListener<T> listener = null;
|
||||
|
||||
public void setChangedListener(Consumer<T> consumer) {
|
||||
this.consumer = Objects.requireNonNull(consumer);
|
||||
this.listener = null;
|
||||
}
|
||||
|
||||
public void setChangedListener(ChangeListener<T> listener) {
|
||||
this.consumer = null;
|
||||
this.listener = Objects.requireNonNull(listener);
|
||||
}
|
||||
|
||||
public void setChangedListenerAndOperate(Consumer<T> listener) {
|
||||
setChangedListener(listener);
|
||||
listener.accept(get());
|
||||
}
|
||||
|
||||
public ImmediateObjectProperty(Object bean, String name, T initialValue) {
|
||||
super(bean, name, initialValue);
|
||||
ChangeListener<T> changeListener = (a, b, newValue) -> {
|
||||
if (consumer != null)
|
||||
consumer.accept(newValue);
|
||||
if (listener != null)
|
||||
listener.changed(a, b, newValue);
|
||||
};
|
||||
addListener(changeListener);
|
||||
}
|
||||
|
||||
@@ -25,9 +25,10 @@ import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
* @deprecated Use SimpleStringProperty instead
|
||||
*/
|
||||
@Deprecated
|
||||
public class ImmediateStringProperty extends SimpleStringProperty {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -63,6 +63,10 @@ public final class JavaVersion {
|
||||
return platform;
|
||||
}
|
||||
|
||||
public VersionNumber getVersionNumber() {
|
||||
return VersionNumber.asVersion(longVersion.replace('_', '.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* The major version of Java installation.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user